fix(observabillity): propagate ctx to every log call and add resource to logging
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 5m5s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 5m36s

This commit was merged in pull request #187.
This commit is contained in:
2025-06-17 09:42:19 +02:00
parent ff3c7bdf52
commit 6c92206b3c
27 changed files with 288 additions and 266 deletions

View File

@@ -53,7 +53,7 @@ func (db AuthSqlite) InsertUser(ctx context.Context, user *types.User) error {
return ErrAlreadyExists return ErrAlreadyExists
} }
slog.Error("SQL error InsertUser", "err", err) slog.ErrorContext(ctx, "SQL error InsertUser", "err", err)
return types.ErrInternal return types.ErrInternal
} }
@@ -68,7 +68,7 @@ func (db AuthSqlite) UpdateUser(ctx context.Context, user *types.User) error {
user.EmailVerified, user.EmailVerifiedAt, user.Password, user.Id) user.EmailVerified, user.EmailVerifiedAt, user.Password, user.Id)
if err != nil { if err != nil {
slog.Error("SQL error UpdateUser", "err", err) slog.ErrorContext(ctx, "SQL error UpdateUser", "err", err)
return types.ErrInternal return types.ErrInternal
} }
@@ -94,7 +94,7 @@ func (db AuthSqlite) GetUserByEmail(ctx context.Context, email string) (*types.U
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
return nil, ErrNotFound return nil, ErrNotFound
} else { } else {
slog.Error("SQL error GetUser", "err", err) slog.ErrorContext(ctx, "SQL error GetUser", "err", err)
return nil, types.ErrInternal return nil, types.ErrInternal
} }
} }
@@ -121,7 +121,7 @@ func (db AuthSqlite) GetUser(ctx context.Context, userId uuid.UUID) (*types.User
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
return nil, ErrNotFound return nil, ErrNotFound
} else { } else {
slog.Error("SQL error GetUser", "err", err) slog.ErrorContext(ctx, "SQL error GetUser", "err", err)
return nil, types.ErrInternal return nil, types.ErrInternal
} }
} }
@@ -132,55 +132,55 @@ func (db AuthSqlite) GetUser(ctx context.Context, userId uuid.UUID) (*types.User
func (db AuthSqlite) DeleteUser(ctx context.Context, userId uuid.UUID) error { func (db AuthSqlite) DeleteUser(ctx context.Context, userId uuid.UUID) error {
tx, err := db.db.BeginTx(ctx, nil) tx, err := db.db.BeginTx(ctx, nil)
if err != nil { if err != nil {
slog.Error("Could not start transaction", "err", err) slog.ErrorContext(ctx, "Could not start transaction", "err", err)
return types.ErrInternal return types.ErrInternal
} }
_, err = tx.ExecContext(ctx, "DELETE FROM account WHERE user_id = ?", userId) _, err = tx.ExecContext(ctx, "DELETE FROM account WHERE user_id = ?", userId)
if err != nil { if err != nil {
_ = tx.Rollback() _ = tx.Rollback()
slog.Error("Could not delete accounts", "err", err) slog.ErrorContext(ctx, "Could not delete accounts", "err", err)
return types.ErrInternal return types.ErrInternal
} }
_, err = tx.ExecContext(ctx, "DELETE FROM token WHERE user_id = ?", userId) _, err = tx.ExecContext(ctx, "DELETE FROM token WHERE user_id = ?", userId)
if err != nil { if err != nil {
_ = tx.Rollback() _ = tx.Rollback()
slog.Error("Could not delete user tokens", "err", err) slog.ErrorContext(ctx, "Could not delete user tokens", "err", err)
return types.ErrInternal return types.ErrInternal
} }
_, err = tx.ExecContext(ctx, "DELETE FROM session WHERE user_id = ?", userId) _, err = tx.ExecContext(ctx, "DELETE FROM session WHERE user_id = ?", userId)
if err != nil { if err != nil {
_ = tx.Rollback() _ = tx.Rollback()
slog.Error("Could not delete sessions", "err", err) slog.ErrorContext(ctx, "Could not delete sessions", "err", err)
return types.ErrInternal return types.ErrInternal
} }
_, err = tx.ExecContext(ctx, "DELETE FROM user WHERE user_id = ?", userId) _, err = tx.ExecContext(ctx, "DELETE FROM user WHERE user_id = ?", userId)
if err != nil { if err != nil {
_ = tx.Rollback() _ = tx.Rollback()
slog.Error("Could not delete user", "err", err) slog.ErrorContext(ctx, "Could not delete user", "err", err)
return types.ErrInternal return types.ErrInternal
} }
_, err = tx.ExecContext(ctx, "DELETE FROM treasure_chest WHERE user_id = ?", userId) _, err = tx.ExecContext(ctx, "DELETE FROM treasure_chest WHERE user_id = ?", userId)
if err != nil { if err != nil {
_ = tx.Rollback() _ = tx.Rollback()
slog.Error("Could not delete user", "err", err) slog.ErrorContext(ctx, "Could not delete user", "err", err)
return types.ErrInternal return types.ErrInternal
} }
_, err = tx.ExecContext(ctx, "DELETE FROM \"transaction\" WHERE user_id = ?", userId) _, err = tx.ExecContext(ctx, "DELETE FROM \"transaction\" WHERE user_id = ?", userId)
if err != nil { if err != nil {
_ = tx.Rollback() _ = tx.Rollback()
slog.Error("Could not delete user", "err", err) slog.ErrorContext(ctx, "Could not delete user", "err", err)
return types.ErrInternal return types.ErrInternal
} }
err = tx.Commit() err = tx.Commit()
if err != nil { if err != nil {
slog.Error("Could not commit transaction", "err", err) slog.ErrorContext(ctx, "Could not commit transaction", "err", err)
return types.ErrInternal return types.ErrInternal
} }
@@ -193,7 +193,7 @@ func (db AuthSqlite) InsertToken(ctx context.Context, token *types.Token) error
VALUES (?, ?, ?, ?, ?, ?)`, token.UserId, token.SessionId, token.Type, token.Token, token.CreatedAt, token.ExpiresAt) VALUES (?, ?, ?, ?, ?, ?)`, token.UserId, token.SessionId, token.Type, token.Token, token.CreatedAt, token.ExpiresAt)
if err != nil { if err != nil {
slog.Error("Could not insert token", "err", err) slog.ErrorContext(ctx, "Could not insert token", "err", err)
return types.ErrInternal return types.ErrInternal
} }
@@ -218,23 +218,23 @@ func (db AuthSqlite) GetToken(ctx context.Context, token string) (*types.Token,
if err != nil { if err != nil {
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
slog.Info("Token not found", "token", token) slog.InfoContext(ctx, "Token not found", "token", token)
return nil, ErrNotFound return nil, ErrNotFound
} else { } else {
slog.Error("Could not get token", "err", err) slog.ErrorContext(ctx, "Could not get token", "err", err)
return nil, types.ErrInternal return nil, types.ErrInternal
} }
} }
createdAt, err = time.Parse(time.RFC3339, createdAtStr) createdAt, err = time.Parse(time.RFC3339, createdAtStr)
if err != nil { if err != nil {
slog.Error("Could not parse token.created_at", "err", err) slog.ErrorContext(ctx, "Could not parse token.created_at", "err", err)
return nil, types.ErrInternal return nil, types.ErrInternal
} }
expiresAt, err = time.Parse(time.RFC3339, expiresAtStr) expiresAt, err = time.Parse(time.RFC3339, expiresAtStr)
if err != nil { if err != nil {
slog.Error("Could not parse token.expires_at", "err", err) slog.ErrorContext(ctx, "Could not parse token.expires_at", "err", err)
return nil, types.ErrInternal return nil, types.ErrInternal
} }
@@ -249,11 +249,11 @@ func (db AuthSqlite) GetTokensByUserIdAndType(ctx context.Context, userId uuid.U
AND type = ?`, userId, tokenType) AND type = ?`, userId, tokenType)
if err != nil { if err != nil {
slog.Error("Could not get token", "err", err) slog.ErrorContext(ctx, "Could not get token", "err", err)
return nil, types.ErrInternal return nil, types.ErrInternal
} }
return getTokensFromQuery(query, userId, "", tokenType) return getTokensFromQuery(ctx, query, userId, "", tokenType)
} }
func (db AuthSqlite) GetTokensBySessionIdAndType(ctx context.Context, sessionId string, tokenType types.TokenType) ([]*types.Token, error) { func (db AuthSqlite) GetTokensBySessionIdAndType(ctx context.Context, sessionId string, tokenType types.TokenType) ([]*types.Token, error) {
@@ -264,14 +264,14 @@ func (db AuthSqlite) GetTokensBySessionIdAndType(ctx context.Context, sessionId
AND type = ?`, sessionId, tokenType) AND type = ?`, sessionId, tokenType)
if err != nil { if err != nil {
slog.Error("Could not get token", "err", err) slog.ErrorContext(ctx, "Could not get token", "err", err)
return nil, types.ErrInternal return nil, types.ErrInternal
} }
return getTokensFromQuery(query, uuid.Nil, sessionId, tokenType) return getTokensFromQuery(ctx, query, uuid.Nil, sessionId, tokenType)
} }
func getTokensFromQuery(query *sql.Rows, userId uuid.UUID, sessionId string, tokenType types.TokenType) ([]*types.Token, error) { func getTokensFromQuery(ctx context.Context, query *sql.Rows, userId uuid.UUID, sessionId string, tokenType types.TokenType) ([]*types.Token, error) {
var tokens []*types.Token var tokens []*types.Token
hasRows := false hasRows := false
@@ -288,19 +288,19 @@ func getTokensFromQuery(query *sql.Rows, userId uuid.UUID, sessionId string, tok
err := query.Scan(&token, &createdAtStr, &expiresAtStr) err := query.Scan(&token, &createdAtStr, &expiresAtStr)
if err != nil { if err != nil {
slog.Error("Could not scan token", "err", err) slog.ErrorContext(ctx, "Could not scan token", "err", err)
return nil, types.ErrInternal return nil, types.ErrInternal
} }
createdAt, err = time.Parse(time.RFC3339, createdAtStr) createdAt, err = time.Parse(time.RFC3339, createdAtStr)
if err != nil { if err != nil {
slog.Error("Could not parse token.created_at", "err", err) slog.ErrorContext(ctx, "Could not parse token.created_at", "err", err)
return nil, types.ErrInternal return nil, types.ErrInternal
} }
expiresAt, err = time.Parse(time.RFC3339, expiresAtStr) expiresAt, err = time.Parse(time.RFC3339, expiresAtStr)
if err != nil { if err != nil {
slog.Error("Could not parse token.expires_at", "err", err) slog.ErrorContext(ctx, "Could not parse token.expires_at", "err", err)
return nil, types.ErrInternal return nil, types.ErrInternal
} }
@@ -317,7 +317,7 @@ func getTokensFromQuery(query *sql.Rows, userId uuid.UUID, sessionId string, tok
func (db AuthSqlite) DeleteToken(ctx context.Context, token string) error { func (db AuthSqlite) DeleteToken(ctx context.Context, token string) error {
_, err := db.db.ExecContext(ctx, "DELETE FROM token WHERE token = ?", token) _, err := db.db.ExecContext(ctx, "DELETE FROM token WHERE token = ?", token)
if err != nil { if err != nil {
slog.Error("Could not delete token", "err", err) slog.ErrorContext(ctx, "Could not delete token", "err", err)
return types.ErrInternal return types.ErrInternal
} }
return nil return nil
@@ -329,7 +329,7 @@ func (db AuthSqlite) InsertSession(ctx context.Context, session *types.Session)
VALUES (?, ?, ?, ?)`, session.Id, session.UserId, session.CreatedAt, session.ExpiresAt) VALUES (?, ?, ?, ?)`, session.Id, session.UserId, session.CreatedAt, session.ExpiresAt)
if err != nil { if err != nil {
slog.Error("Could not insert new session", "err", err) slog.ErrorContext(ctx, "Could not insert new session", "err", err)
return types.ErrInternal return types.ErrInternal
} }
@@ -349,7 +349,7 @@ func (db AuthSqlite) GetSession(ctx context.Context, sessionId string) (*types.S
WHERE session_id = ?`, sessionId).Scan(&userId, &createdAt, &expiresAt) WHERE session_id = ?`, sessionId).Scan(&userId, &createdAt, &expiresAt)
if err != nil { if err != nil {
slog.Warn("Session not found", "session-id", sessionId, "err", err) slog.WarnContext(ctx, "Session not found", "session-id", sessionId, "err", err)
return nil, ErrNotFound return nil, ErrNotFound
} }
@@ -363,7 +363,7 @@ func (db AuthSqlite) GetSessions(ctx context.Context, userId uuid.UUID) ([]*type
FROM session FROM session
WHERE user_id = ?`, userId) WHERE user_id = ?`, userId)
if err != nil { if err != nil {
slog.Error("Could not get sessions", "err", err) slog.ErrorContext(ctx, "Could not get sessions", "err", err)
return nil, types.ErrInternal return nil, types.ErrInternal
} }
@@ -374,7 +374,7 @@ func (db AuthSqlite) DeleteSession(ctx context.Context, sessionId string) error
if sessionId != "" { if sessionId != "" {
_, err := db.db.ExecContext(ctx, "DELETE FROM session WHERE session_id = ?", sessionId) _, err := db.db.ExecContext(ctx, "DELETE FROM session WHERE session_id = ?", sessionId)
if err != nil { if err != nil {
slog.Error("Could not delete session", "err", err) slog.ErrorContext(ctx, "Could not delete session", "err", err)
return types.ErrInternal return types.ErrInternal
} }
} }
@@ -387,7 +387,7 @@ func (db AuthSqlite) DeleteOldSessions(ctx context.Context) error {
DELETE FROM session DELETE FROM session
WHERE expires_at < datetime('now')`) WHERE expires_at < datetime('now')`)
if err != nil { if err != nil {
slog.Error("Could not delete old sessions", "err", err) slog.ErrorContext(ctx, "Could not delete old sessions", "err", err)
return types.ErrInternal return types.ErrInternal
} }
return nil return nil
@@ -398,7 +398,7 @@ func (db AuthSqlite) DeleteOldTokens(ctx context.Context) error {
DELETE FROM token DELETE FROM token
WHERE expires_at < datetime('now')`) WHERE expires_at < datetime('now')`)
if err != nil { if err != nil {
slog.Error("Could not delete old tokens", "err", err) slog.ErrorContext(ctx, "Could not delete old tokens", "err", err)
return types.ErrInternal return types.ErrInternal
} }
return nil return nil

View File

@@ -1,6 +1,7 @@
package db package db
import ( import (
"context"
"database/sql" "database/sql"
"errors" "errors"
"log/slog" "log/slog"
@@ -12,24 +13,24 @@ var (
ErrAlreadyExists = errors.New("row already exists") ErrAlreadyExists = errors.New("row already exists")
) )
func TransformAndLogDbError(module string, r sql.Result, err error) error { func TransformAndLogDbError(ctx context.Context, module string, r sql.Result, err error) error {
if err != nil { if err != nil {
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
return ErrNotFound return ErrNotFound
} }
slog.Error("database sql", "module", module, "err", err) slog.ErrorContext(ctx, "database sql", "module", module, "err", err)
return types.ErrInternal return types.ErrInternal
} }
if r != nil { if r != nil {
rows, err := r.RowsAffected() rows, err := r.RowsAffected()
if err != nil { if err != nil {
slog.Error("database rows affected", "module", module, "err", err) slog.ErrorContext(ctx, "database rows affected", "module", module, "err", err)
return types.ErrInternal return types.ErrInternal
} }
if rows == 0 { if rows == 0 {
slog.Info("row not found", "module", module) slog.InfoContext(ctx, "row not found", "module", module)
return ErrNotFound return ErrNotFound
} }
} }

View File

@@ -24,7 +24,7 @@ func (l migrationLogger) Verbose() bool {
func RunMigrations(ctx context.Context, db *sqlx.DB, pathPrefix string) error { func RunMigrations(ctx context.Context, db *sqlx.DB, pathPrefix string) error {
driver, err := sqlite3.WithInstance(db.DB, &sqlite3.Config{}) driver, err := sqlite3.WithInstance(db.DB, &sqlite3.Config{})
if err != nil { if err != nil {
slog.Error("Could not create Migration instance", "err", err) slog.ErrorContext(ctx, "Could not create Migration instance", "err", err)
return types.ErrInternal return types.ErrInternal
} }
@@ -33,14 +33,14 @@ func RunMigrations(ctx context.Context, db *sqlx.DB, pathPrefix string) error {
"", "",
driver) driver)
if err != nil { if err != nil {
slog.Error("Could not create migrations instance", "err", err) slog.ErrorContext(ctx, "Could not create migrations instance", "err", err)
return types.ErrInternal return types.ErrInternal
} }
m.Log = migrationLogger{} m.Log = migrationLogger{}
if err = m.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) { if err = m.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
slog.Error("Could not run migrations", "err", err) slog.ErrorContext(ctx, "Could not run migrations", "err", err)
return types.ErrInternal return types.ErrInternal
} }

View File

@@ -39,7 +39,7 @@ func Run(ctx context.Context, database *sqlx.DB, migrationsPrefix string, env fu
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
err = otelShutdown(ctx) err = otelShutdown(ctx)
if err != nil { if err != nil {
slog.Error("error shutting down OpenTelemetry SDK", "err", err) slog.ErrorContext(ctx, "error shutting down OpenTelemetry SDK", "err", err)
} }
cancel() cancel()
}() }()
@@ -47,10 +47,10 @@ func Run(ctx context.Context, database *sqlx.DB, migrationsPrefix string, env fu
slog.SetDefault(log.NewLogPropagator()) slog.SetDefault(log.NewLogPropagator())
} }
slog.Info("Starting server...") slog.InfoContext(ctx, "Starting server...")
// init server settings // init server settings
serverSettings, err := types.NewSettingsFromEnv(env) serverSettings, err := types.NewSettingsFromEnv(ctx, env)
if err != nil { if err != nil {
return err return err
} }
@@ -67,26 +67,26 @@ func Run(ctx context.Context, database *sqlx.DB, migrationsPrefix string, env fu
Handler: createHandlerWithServices(ctx, database, serverSettings), Handler: createHandlerWithServices(ctx, database, serverSettings),
ReadHeaderTimeout: 2 * time.Second, ReadHeaderTimeout: 2 * time.Second,
} }
go startServer(httpServer) go startServer(ctx, httpServer)
// graceful shutdown // graceful shutdown
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(1) wg.Add(1)
go shutdownServer(httpServer, ctx, &wg) go shutdownServer(ctx, httpServer, &wg)
wg.Wait() wg.Wait()
return nil return nil
} }
func startServer(s *http.Server) { func startServer(ctx context.Context, s *http.Server) {
slog.Info("Starting server", "addr", s.Addr) slog.InfoContext(ctx, "Starting server", "addr", s.Addr)
if err := s.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { if err := s.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
slog.Error("error listening and serving", "err", err) slog.ErrorContext(ctx, "error listening and serving", "err", err)
} }
} }
func shutdownServer(s *http.Server, ctx context.Context, wg *sync.WaitGroup) { func shutdownServer(ctx context.Context, s *http.Server, wg *sync.WaitGroup) {
defer wg.Done() defer wg.Done()
if s == nil { if s == nil {
return return
@@ -97,9 +97,9 @@ func shutdownServer(s *http.Server, ctx context.Context, wg *sync.WaitGroup) {
shutdownCtx, cancel := context.WithTimeout(shutdownCtx, 10*time.Second) shutdownCtx, cancel := context.WithTimeout(shutdownCtx, 10*time.Second)
defer cancel() defer cancel()
if err := s.Shutdown(shutdownCtx); err != nil { if err := s.Shutdown(shutdownCtx); err != nil {
slog.Error("error shutting down http server", "err", err) slog.ErrorContext(ctx, "error shutting down http server", "err", err)
} else { } else {
slog.Info("Gracefully stopped http server", "addr", s.Addr) slog.InfoContext(ctx, "Gracefully stopped http server", "addr", s.Addr)
} }
} }
@@ -170,7 +170,7 @@ func dailyTaskTimer(ctx context.Context, transactionRecurring service.Transactio
} }
func runDailyTasks(ctx context.Context, transactionRecurring service.TransactionRecurring, auth service.Auth) { func runDailyTasks(ctx context.Context, transactionRecurring service.TransactionRecurring, auth service.Auth) {
slog.Info("Running daily tasks") slog.InfoContext(ctx, "Running daily tasks")
_ = transactionRecurring.GenerateTransactions(ctx) _ = transactionRecurring.GenerateTransactions(ctx)
_ = auth.CleanupSessionsAndTokens(ctx) _ = auth.CleanupSessionsAndTokens(ctx)
} }

View File

@@ -98,9 +98,9 @@ func (handler AuthImpl) handleSignIn() http.HandlerFunc {
if err != nil { if err != nil {
if errors.Is(err, service.ErrInvalidCredentials) { if errors.Is(err, service.ErrInvalidCredentials) {
utils.TriggerToastWithStatus(w, r, "error", "Invalid email or password", http.StatusUnauthorized) utils.TriggerToastWithStatus(r.Context(), w, r, "error", "Invalid email or password", http.StatusUnauthorized)
} else { } else {
utils.TriggerToastWithStatus(w, r, "error", "An error occurred", http.StatusInternalServerError) utils.TriggerToastWithStatus(r.Context(), w, r, "error", "An error occurred", http.StatusInternalServerError)
} }
return return
} }
@@ -167,7 +167,7 @@ func (handler AuthImpl) handleVerifyResendComp() http.HandlerFunc {
_, err := w.Write([]byte("<p class=\"mt-8\">Verification email sent</p>")) _, err := w.Write([]byte("<p class=\"mt-8\">Verification email sent</p>"))
if err != nil { if err != nil {
slog.Error("Could not write response", "err", err) slog.ErrorContext(r.Context(), "Could not write response", "err", err)
} }
} }
} }
@@ -202,13 +202,13 @@ func (handler AuthImpl) handleSignUp() http.HandlerFunc {
var password = r.FormValue("password") var password = r.FormValue("password")
_, err := utils.WaitMinimumTime(securityWaitDuration, func() (any, error) { _, err := utils.WaitMinimumTime(securityWaitDuration, func() (any, error) {
slog.Info("signing up", "email", email) slog.InfoContext(r.Context(), "signing up", "email", email)
user, err := handler.service.SignUp(r.Context(), email, password) user, err := handler.service.SignUp(r.Context(), email, password)
if err != nil { if err != nil {
return nil, err return nil, err
} }
slog.Info("Sending verification email", "to", user.Email) slog.InfoContext(r.Context(), "Sending verification email", "to", user.Email)
go handler.service.SendVerificationMail(r.Context(), user.Id, user.Email) go handler.service.SendVerificationMail(r.Context(), user.Id, user.Email)
return nil, nil return nil, nil
}) })
@@ -216,19 +216,19 @@ func (handler AuthImpl) handleSignUp() http.HandlerFunc {
if err != nil { if err != nil {
switch { switch {
case errors.Is(err, types.ErrInternal): case errors.Is(err, types.ErrInternal):
utils.TriggerToastWithStatus(w, r, "error", "An error occurred", http.StatusInternalServerError) utils.TriggerToastWithStatus(r.Context(), w, r, "error", "An error occurred", http.StatusInternalServerError)
return return
case errors.Is(err, service.ErrInvalidEmail): case errors.Is(err, service.ErrInvalidEmail):
utils.TriggerToastWithStatus(w, r, "error", "The email provided is invalid", http.StatusBadRequest) utils.TriggerToastWithStatus(r.Context(), w, r, "error", "The email provided is invalid", http.StatusBadRequest)
return return
case errors.Is(err, service.ErrInvalidPassword): case errors.Is(err, service.ErrInvalidPassword):
utils.TriggerToastWithStatus(w, r, "error", service.ErrInvalidPassword.Error(), http.StatusBadRequest) utils.TriggerToastWithStatus(r.Context(), w, r, "error", service.ErrInvalidPassword.Error(), http.StatusBadRequest)
return return
} }
// If err is "service.ErrAccountExists", then just continue // If err is "service.ErrAccountExists", then just continue
} }
utils.TriggerToastWithStatus(w, r, "success", "An activation link has been send to your email", http.StatusOK) utils.TriggerToastWithStatus(r.Context(), w, r, "success", "An activation link has been send to your email", http.StatusOK)
} }
} }
@@ -291,9 +291,9 @@ func (handler AuthImpl) handleDeleteAccountComp() http.HandlerFunc {
err := handler.service.DeleteAccount(r.Context(), user, password) err := handler.service.DeleteAccount(r.Context(), user, password)
if err != nil { if err != nil {
if errors.Is(err, service.ErrInvalidCredentials) { if errors.Is(err, service.ErrInvalidCredentials) {
utils.TriggerToastWithStatus(w, r, "error", "Password not correct", http.StatusBadRequest) utils.TriggerToastWithStatus(r.Context(), w, r, "error", "Password not correct", http.StatusBadRequest)
} else { } else {
utils.TriggerToastWithStatus(w, r, "error", "Internal Server Error", http.StatusInternalServerError) utils.TriggerToastWithStatus(r.Context(), w, r, "error", "Internal Server Error", http.StatusInternalServerError)
} }
return return
} }
@@ -327,7 +327,7 @@ func (handler AuthImpl) handleChangePasswordComp() http.HandlerFunc {
session := middleware.GetSession(r) session := middleware.GetSession(r)
user := middleware.GetUser(r) user := middleware.GetUser(r)
if session == nil || user == nil { if session == nil || user == nil {
utils.TriggerToastWithStatus(w, r, "error", "Unathorized", http.StatusUnauthorized) utils.TriggerToastWithStatus(r.Context(), w, r, "error", "Unathorized", http.StatusUnauthorized)
return return
} }
@@ -336,11 +336,11 @@ func (handler AuthImpl) handleChangePasswordComp() http.HandlerFunc {
err := handler.service.ChangePassword(r.Context(), user, session.Id, currPass, newPass) err := handler.service.ChangePassword(r.Context(), user, session.Id, currPass, newPass)
if err != nil { if err != nil {
utils.TriggerToastWithStatus(w, r, "error", err.Error(), http.StatusBadRequest) utils.TriggerToastWithStatus(r.Context(), w, r, "error", err.Error(), http.StatusBadRequest)
return return
} }
utils.TriggerToastWithStatus(w, r, "success", "Password changed", http.StatusOK) utils.TriggerToastWithStatus(r.Context(), w, r, "success", "Password changed", http.StatusOK)
} }
} }
@@ -365,7 +365,7 @@ func (handler AuthImpl) handleForgotPasswordComp() http.HandlerFunc {
email := r.FormValue("email") email := r.FormValue("email")
if email == "" { if email == "" {
utils.TriggerToastWithStatus(w, r, "error", "Please enter an email", http.StatusBadRequest) utils.TriggerToastWithStatus(r.Context(), w, r, "error", "Please enter an email", http.StatusBadRequest)
return return
} }
@@ -375,9 +375,9 @@ func (handler AuthImpl) handleForgotPasswordComp() http.HandlerFunc {
}) })
if err != nil { if err != nil {
utils.TriggerToastWithStatus(w, r, "error", "Internal Server Error", http.StatusInternalServerError) utils.TriggerToastWithStatus(r.Context(), w, r, "error", "Internal Server Error", http.StatusInternalServerError)
} else { } else {
utils.TriggerToastWithStatus(w, r, "info", "If the address exists, an email has been sent.", http.StatusOK) utils.TriggerToastWithStatus(r.Context(), w, r, "info", "If the address exists, an email has been sent.", http.StatusOK)
} }
} }
} }
@@ -388,8 +388,8 @@ func (handler AuthImpl) handleForgotPasswordResponseComp() http.HandlerFunc {
pageUrl, err := url.Parse(r.Header.Get("Hx-Current-Url")) pageUrl, err := url.Parse(r.Header.Get("Hx-Current-Url"))
if err != nil { if err != nil {
slog.Error("Could not get current URL", "err", err) slog.ErrorContext(r.Context(), "Could not get current URL", "err", err)
utils.TriggerToastWithStatus(w, r, "error", "Internal Server Error", http.StatusInternalServerError) utils.TriggerToastWithStatus(r.Context(), w, r, "error", "Internal Server Error", http.StatusInternalServerError)
return return
} }
@@ -398,9 +398,9 @@ func (handler AuthImpl) handleForgotPasswordResponseComp() http.HandlerFunc {
err = handler.service.ForgotPassword(r.Context(), token, newPass) err = handler.service.ForgotPassword(r.Context(), token, newPass)
if err != nil { if err != nil {
utils.TriggerToastWithStatus(w, r, "error", err.Error(), http.StatusBadRequest) utils.TriggerToastWithStatus(r.Context(), w, r, "error", err.Error(), http.StatusBadRequest)
} else { } else {
utils.TriggerToastWithStatus(w, r, "success", "Password changed", http.StatusOK) utils.TriggerToastWithStatus(r.Context(), w, r, "success", "Password changed", http.StatusOK)
} }
} }
} }

View File

@@ -15,17 +15,17 @@ import (
func handleError(w http.ResponseWriter, r *http.Request, err error) { func handleError(w http.ResponseWriter, r *http.Request, err error) {
switch { switch {
case errors.Is(err, service.ErrUnauthorized): case errors.Is(err, service.ErrUnauthorized):
utils.TriggerToastWithStatus(w, r, "error", "You are not autorized to perform this operation.", http.StatusUnauthorized) utils.TriggerToastWithStatus(r.Context(), w, r, "error", "You are not autorized to perform this operation.", http.StatusUnauthorized)
return return
case errors.Is(err, service.ErrBadRequest): case errors.Is(err, service.ErrBadRequest):
utils.TriggerToastWithStatus(w, r, "error", extractErrorMessage(err), http.StatusBadRequest) utils.TriggerToastWithStatus(r.Context(), w, r, "error", extractErrorMessage(err), http.StatusBadRequest)
return return
case errors.Is(err, db.ErrNotFound): case errors.Is(err, db.ErrNotFound):
utils.TriggerToastWithStatus(w, r, "error", extractErrorMessage(err), http.StatusNotFound) utils.TriggerToastWithStatus(r.Context(), w, r, "error", extractErrorMessage(err), http.StatusNotFound)
return return
} }
utils.TriggerToastWithStatus(w, r, "error", "Internal Server Error", http.StatusInternalServerError) utils.TriggerToastWithStatus(r.Context(), w, r, "error", "Internal Server Error", http.StatusInternalServerError)
} }
func extractErrorMessage(err error) string { func extractErrorMessage(err error) string {

View File

@@ -42,9 +42,9 @@ func CrossSiteRequestForgery(auth service.Auth) func(http.Handler) http.Handler
csrfToken := r.Header.Get("Csrf-Token") csrfToken := r.Header.Get("Csrf-Token")
if session == nil || csrfToken == "" || !auth.IsCsrfTokenValid(ctx, csrfToken, session.Id) { if session == nil || csrfToken == "" || !auth.IsCsrfTokenValid(ctx, csrfToken, session.Id) {
slog.Info("CSRF-Token not correct", "token", csrfToken) slog.InfoContext(ctx, "CSRF-Token not correct", "token", csrfToken)
if r.Header.Get("Hx-Request") == "true" { if r.Header.Get("Hx-Request") == "true" {
utils.TriggerToastWithStatus(w, r, "error", "CSRF-Token not correct", http.StatusBadRequest) utils.TriggerToastWithStatus(ctx, w, r, "error", "CSRF-Token not correct", http.StatusBadRequest)
} else { } else {
http.Error(w, "CSRF-Token not correct", http.StatusBadRequest) http.Error(w, "CSRF-Token not correct", http.StatusBadRequest)
} }
@@ -55,7 +55,7 @@ func CrossSiteRequestForgery(auth service.Auth) func(http.Handler) http.Handler
token, err := auth.GetCsrfToken(ctx, session) token, err := auth.GetCsrfToken(ctx, session)
if err != nil { if err != nil {
if r.Header.Get("Hx-Request") == "true" { if r.Header.Get("Hx-Request") == "true" {
utils.TriggerToastWithStatus(w, r, "error", "Could not generate CSRF Token", http.StatusBadRequest) utils.TriggerToastWithStatus(ctx, w, r, "error", "Could not generate CSRF Token", http.StatusBadRequest)
} else { } else {
http.Error(w, "Could not generate CSRF Token", http.StatusBadRequest) http.Error(w, "Could not generate CSRF Token", http.StatusBadRequest)
} }

View File

@@ -33,7 +33,7 @@ func Gzip(next http.Handler) http.Handler {
err := gz.Close() err := gz.Close()
if err != nil && !errors.Is(err, http.ErrBodyNotAllowed) { if err != nil && !errors.Is(err, http.ErrBodyNotAllowed) {
slog.Error("Gzip: could not close Writer", "err", err) slog.ErrorContext(r.Context(), "Gzip: could not close Writer", "err", err)
} }
}) })
} }

View File

@@ -26,7 +26,7 @@ func Log(next http.Handler) http.Handler {
} }
next.ServeHTTP(wrapped, r) next.ServeHTTP(wrapped, r)
slog.Info("request", slog.InfoContext(r.Context(), "request",
"remoteAddr", r.RemoteAddr, "remoteAddr", r.RemoteAddr,
"status", wrapped.StatusCode, "status", wrapped.StatusCode,
"method", r.Method, "method", r.Method,

View File

@@ -22,7 +22,7 @@ func (render *Render) RenderWithStatus(r *http.Request, w http.ResponseWriter, c
w.WriteHeader(status) w.WriteHeader(status)
err := comp.Render(r.Context(), w) err := comp.Render(r.Context(), w)
if err != nil { if err != nil {
slog.Error("Failed to render layout", "err", err) slog.ErrorContext(r.Context(), "Failed to render layout", "err", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError) http.Error(w, "Internal Server Error", http.StatusInternalServerError)
} }
} }

View File

@@ -57,7 +57,7 @@ func (handler IndexImpl) handleRootAnd404() http.HandlerFunc {
var err error var err error
comp, err = handler.dashboard(user, htmx, r) comp, err = handler.dashboard(user, htmx, r)
if err != nil { if err != nil {
slog.Error("Failed to get dashboard summary", "err", err) slog.ErrorContext(r.Context(), "Failed to get dashboard summary", "err", err)
} }
} else { } else {
comp = template.Index() comp = template.Index()

View File

@@ -254,7 +254,7 @@ func (h TransactionImpl) handleRecalculate() http.HandlerFunc {
return return
} }
utils.TriggerToastWithStatus(w, r, "success", "Balances recalculated, please refresh", http.StatusOK) utils.TriggerToastWithStatus(r.Context(), w, r, "success", "Balances recalculated, please refresh", http.StatusOK)
} }
} }

View File

@@ -115,7 +115,7 @@ func (h TransactionRecurringImpl) renderItems(w http.ResponseWriter, r *http.Req
var transactionsRecurring []*types.TransactionRecurring var transactionsRecurring []*types.TransactionRecurring
var err error var err error
if accountId == "" && treasureChestId == "" { if accountId == "" && treasureChestId == "" {
utils.TriggerToastWithStatus(w, r, "error", "Please select an account or treasure chest", http.StatusBadRequest) utils.TriggerToastWithStatus(r.Context(), w, r, "error", "Please select an account or treasure chest", http.StatusBadRequest)
} }
if accountId != "" { if accountId != "" {
transactionsRecurring, err = h.s.GetAllByAccount(r.Context(), user, accountId) transactionsRecurring, err = h.s.GetAllByAccount(r.Context(), user, accountId)

View File

@@ -27,8 +27,6 @@ func (l *logHandler) Enabled(ctx context.Context, level slog.Level) bool {
// Handle implements slog.Handler. // Handle implements slog.Handler.
func (l *logHandler) Handle(ctx context.Context, rec slog.Record) error { func (l *logHandler) Handle(ctx context.Context, rec slog.Record) error {
rec.AddAttrs(slog.String("service_name", "spend-sparrow"))
if err := l.console.Handle(ctx, rec); err != nil { if err := l.console.Handle(ctx, rec); err != nil {
return err return err
} }

View File

@@ -3,6 +3,7 @@ package internal
import ( import (
"context" "context"
"errors" "errors"
"log/slog"
"time" "time"
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel"
@@ -13,7 +14,9 @@ import (
"go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/log" "go.opentelemetry.io/otel/sdk/log"
"go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
"go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
) )
var ( var (
@@ -47,6 +50,14 @@ func setupOTelSDK(ctx context.Context) (func(context.Context) error, error) {
prop := newPropagator() prop := newPropagator()
otel.SetTextMapPropagator(prop) otel.SetTextMapPropagator(prop)
resources, err := resource.New(
ctx,
resource.WithAttributes(semconv.ServiceName("spend-sparrow")),
)
if err != nil {
slog.ErrorContext(ctx, "failed to create resource", "error", err)
}
// Set up trace provider. // Set up trace provider.
tracerProvider, err := newTracerProvider(ctx) tracerProvider, err := newTracerProvider(ctx)
if err != nil { if err != nil {
@@ -66,7 +77,7 @@ func setupOTelSDK(ctx context.Context) (func(context.Context) error, error) {
otel.SetMeterProvider(meterProvider) otel.SetMeterProvider(meterProvider)
// Set up logger provider. // Set up logger provider.
loggerProvider, err := newLoggerProvider(ctx) loggerProvider, err := newLoggerProvider(ctx, resources)
if err != nil { if err != nil {
handleErr(ctx, err) handleErr(ctx, err)
return nil, err return nil, err
@@ -85,7 +96,8 @@ func newPropagator() propagation.TextMapPropagator {
} }
func newTracerProvider(ctx context.Context) (*trace.TracerProvider, error) { func newTracerProvider(ctx context.Context) (*trace.TracerProvider, error) {
exp, err := otlptracegrpc.New(ctx, exp, err := otlptracegrpc.New(
ctx,
otlptracegrpc.WithEndpoint(otelEndpoint), otlptracegrpc.WithEndpoint(otelEndpoint),
otlptracegrpc.WithInsecure(), otlptracegrpc.WithInsecure(),
) )
@@ -97,7 +109,8 @@ func newTracerProvider(ctx context.Context) (*trace.TracerProvider, error) {
} }
func newMeterProvider(ctx context.Context) (*metric.MeterProvider, error) { func newMeterProvider(ctx context.Context) (*metric.MeterProvider, error) {
exp, err := otlpmetricgrpc.New(ctx, exp, err := otlpmetricgrpc.New(
ctx,
otlpmetricgrpc.WithInsecure(), otlpmetricgrpc.WithInsecure(),
otlpmetricgrpc.WithEndpoint(otelEndpoint)) otlpmetricgrpc.WithEndpoint(otelEndpoint))
if err != nil { if err != nil {
@@ -110,7 +123,7 @@ func newMeterProvider(ctx context.Context) (*metric.MeterProvider, error) {
exp, metric.WithInterval(15*time.Second)))), nil exp, metric.WithInterval(15*time.Second)))), nil
} }
func newLoggerProvider(ctx context.Context) (*log.LoggerProvider, error) { func newLoggerProvider(ctx context.Context, resource *resource.Resource) (*log.LoggerProvider, error) {
logExporter, err := otlploggrpc.New( logExporter, err := otlploggrpc.New(
ctx, ctx,
otlploggrpc.WithInsecure(), otlploggrpc.WithInsecure(),
@@ -121,6 +134,7 @@ func newLoggerProvider(ctx context.Context) (*log.LoggerProvider, error) {
loggerProvider := log.NewLoggerProvider( loggerProvider := log.NewLoggerProvider(
log.WithProcessor(log.NewBatchProcessor(logExporter)), log.WithProcessor(log.NewBatchProcessor(logExporter)),
log.WithResource(resource),
) )
return loggerProvider, nil return loggerProvider, nil
} }

View File

@@ -39,7 +39,7 @@ func (s AccountImpl) Add(ctx context.Context, user *types.User, name string) (*t
return nil, ErrUnauthorized return nil, ErrUnauthorized
} }
newId, err := s.random.UUID() newId, err := s.random.UUID(ctx)
if err != nil { if err != nil {
return nil, types.ErrInternal return nil, types.ErrInternal
} }
@@ -68,7 +68,7 @@ func (s AccountImpl) Add(ctx context.Context, user *types.User, name string) (*t
r, err := s.db.NamedExecContext(ctx, ` r, err := s.db.NamedExecContext(ctx, `
INSERT INTO account (id, user_id, name, current_balance, oink_balance, created_at, created_by) INSERT INTO account (id, user_id, name, current_balance, oink_balance, created_at, created_by)
VALUES (:id, :user_id, :name, :current_balance, :oink_balance, :created_at, :created_by)`, account) VALUES (:id, :user_id, :name, :current_balance, :oink_balance, :created_at, :created_by)`, account)
err = db.TransformAndLogDbError("account Insert", r, err) err = db.TransformAndLogDbError(ctx, "account Insert", r, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -86,12 +86,12 @@ func (s AccountImpl) UpdateName(ctx context.Context, user *types.User, id string
} }
uuid, err := uuid.Parse(id) uuid, err := uuid.Parse(id)
if err != nil { if err != nil {
slog.Error("account update", "err", err) slog.ErrorContext(ctx, "account update", "err", err)
return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest) return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest)
} }
tx, err := s.db.BeginTxx(ctx, nil) tx, err := s.db.BeginTxx(ctx, nil)
err = db.TransformAndLogDbError("account Update", nil, err) err = db.TransformAndLogDbError(ctx, "account Update", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -101,7 +101,7 @@ func (s AccountImpl) UpdateName(ctx context.Context, user *types.User, id string
var account types.Account var account types.Account
err = tx.GetContext(ctx, &account, `SELECT * FROM account WHERE user_id = ? AND id = ?`, user.Id, uuid) err = tx.GetContext(ctx, &account, `SELECT * FROM account WHERE user_id = ? AND id = ?`, user.Id, uuid)
err = db.TransformAndLogDbError("account Update", nil, err) err = db.TransformAndLogDbError(ctx, "account Update", nil, err)
if err != nil { if err != nil {
if errors.Is(err, db.ErrNotFound) { if errors.Is(err, db.ErrNotFound) {
return nil, fmt.Errorf("account %v not found: %w", id, ErrBadRequest) return nil, fmt.Errorf("account %v not found: %w", id, ErrBadRequest)
@@ -122,13 +122,13 @@ func (s AccountImpl) UpdateName(ctx context.Context, user *types.User, id string
updated_by = :updated_by updated_by = :updated_by
WHERE id = :id WHERE id = :id
AND user_id = :user_id`, account) AND user_id = :user_id`, account)
err = db.TransformAndLogDbError("account Update", r, err) err = db.TransformAndLogDbError(ctx, "account Update", r, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = tx.Commit() err = tx.Commit()
err = db.TransformAndLogDbError("account Update", nil, err) err = db.TransformAndLogDbError(ctx, "account Update", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -142,16 +142,16 @@ func (s AccountImpl) Get(ctx context.Context, user *types.User, id string) (*typ
} }
uuid, err := uuid.Parse(id) uuid, err := uuid.Parse(id)
if err != nil { if err != nil {
slog.Error("account get", "err", err) slog.ErrorContext(ctx, "account get", "err", err)
return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest) return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest)
} }
var account types.Account var account types.Account
err = s.db.GetContext(ctx, &account, ` err = s.db.GetContext(ctx, &account, `
SELECT * FROM account WHERE user_id = ? AND id = ?`, user.Id, uuid) SELECT * FROM account WHERE user_id = ? AND id = ?`, user.Id, uuid)
err = db.TransformAndLogDbError("account Get", nil, err) err = db.TransformAndLogDbError(ctx, "account Get", nil, err)
if err != nil { if err != nil {
slog.Error("account get", "err", err) slog.ErrorContext(ctx, "account get", "err", err)
return nil, err return nil, err
} }
@@ -166,7 +166,7 @@ func (s AccountImpl) GetAll(ctx context.Context, user *types.User) ([]*types.Acc
accounts := make([]*types.Account, 0) accounts := make([]*types.Account, 0)
err := s.db.SelectContext(ctx, &accounts, ` err := s.db.SelectContext(ctx, &accounts, `
SELECT * FROM account WHERE user_id = ? ORDER BY name`, user.Id) SELECT * FROM account WHERE user_id = ? ORDER BY name`, user.Id)
err = db.TransformAndLogDbError("account GetAll", nil, err) err = db.TransformAndLogDbError(ctx, "account GetAll", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -180,12 +180,12 @@ func (s AccountImpl) Delete(ctx context.Context, user *types.User, id string) er
} }
uuid, err := uuid.Parse(id) uuid, err := uuid.Parse(id)
if err != nil { if err != nil {
slog.Error("account delete", "err", err) slog.ErrorContext(ctx, "account delete", "err", err)
return fmt.Errorf("could not parse Id: %w", ErrBadRequest) return fmt.Errorf("could not parse Id: %w", ErrBadRequest)
} }
tx, err := s.db.BeginTxx(ctx, nil) tx, err := s.db.BeginTxx(ctx, nil)
err = db.TransformAndLogDbError("account Delete", nil, err) err = db.TransformAndLogDbError(ctx, "account Delete", nil, err)
if err != nil { if err != nil {
return err return err
} }
@@ -195,7 +195,7 @@ func (s AccountImpl) Delete(ctx context.Context, user *types.User, id string) er
transactionsCount := 0 transactionsCount := 0
err = tx.GetContext(ctx, &transactionsCount, `SELECT COUNT(*) FROM "transaction" WHERE user_id = ? AND account_id = ?`, user.Id, uuid) err = tx.GetContext(ctx, &transactionsCount, `SELECT COUNT(*) FROM "transaction" WHERE user_id = ? AND account_id = ?`, user.Id, uuid)
err = db.TransformAndLogDbError("account Delete", nil, err) err = db.TransformAndLogDbError(ctx, "account Delete", nil, err)
if err != nil { if err != nil {
return err return err
} }
@@ -204,13 +204,13 @@ func (s AccountImpl) Delete(ctx context.Context, user *types.User, id string) er
} }
res, err := tx.ExecContext(ctx, "DELETE FROM account WHERE id = ? and user_id = ?", uuid, user.Id) res, err := tx.ExecContext(ctx, "DELETE FROM account WHERE id = ? and user_id = ?", uuid, user.Id)
err = db.TransformAndLogDbError("account Delete", res, err) err = db.TransformAndLogDbError(ctx, "account Delete", res, err)
if err != nil { if err != nil {
return err return err
} }
err = tx.Commit() err = tx.Commit()
err = db.TransformAndLogDbError("account Delete", nil, err) err = db.TransformAndLogDbError(ctx, "account Delete", nil, err)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -138,7 +138,7 @@ func (service AuthImpl) SignInAnonymous(ctx context.Context) (*types.Session, er
return nil, types.ErrInternal return nil, types.ErrInternal
} }
slog.Info("anonymous session created", "session-id", session.Id) slog.InfoContext(ctx, "anonymous session created", "session-id", session.Id)
return session, nil return session, nil
} }
@@ -153,12 +153,12 @@ func (service AuthImpl) SignUp(ctx context.Context, email string, password strin
return nil, ErrInvalidPassword return nil, ErrInvalidPassword
} }
userId, err := service.random.UUID() userId, err := service.random.UUID(ctx)
if err != nil { if err != nil {
return nil, types.ErrInternal return nil, types.ErrInternal
} }
salt, err := service.random.Bytes(16) salt, err := service.random.Bytes(ctx, 16)
if err != nil { if err != nil {
return nil, types.ErrInternal return nil, types.ErrInternal
} }
@@ -192,7 +192,7 @@ func (service AuthImpl) SendVerificationMail(ctx context.Context, userId uuid.UU
} }
if token == nil { if token == nil {
newTokenStr, err := service.random.String(32) newTokenStr, err := service.random.String(ctx, 32)
if err != nil { if err != nil {
return return
} }
@@ -214,11 +214,11 @@ func (service AuthImpl) SendVerificationMail(ctx context.Context, userId uuid.UU
var w strings.Builder var w strings.Builder
err = mailTemplate.Register(service.serverSettings.BaseUrl, token.Token).Render(context.Background(), &w) err = mailTemplate.Register(service.serverSettings.BaseUrl, token.Token).Render(context.Background(), &w)
if err != nil { if err != nil {
slog.Error("Could not render welcome email", "err", err) slog.ErrorContext(ctx, "Could not render welcome email", "err", err)
return return
} }
service.mail.SendMail(email, "Welcome to spend-sparrow", w.String()) service.mail.SendMail(ctx, email, "Welcome to spend-sparrow", w.String())
} }
func (service AuthImpl) VerifyUserEmail(ctx context.Context, tokenStr string) error { func (service AuthImpl) VerifyUserEmail(ctx context.Context, tokenStr string) error {
@@ -278,7 +278,7 @@ func (service AuthImpl) DeleteAccount(ctx context.Context, user *types.User, cur
return err return err
} }
service.mail.SendMail(user.Email, "Account deleted", "Your account has been deleted") service.mail.SendMail(ctx, user.Email, "Account deleted", "Your account has been deleted")
return nil return nil
} }
@@ -323,7 +323,7 @@ func (service AuthImpl) ChangePassword(ctx context.Context, user *types.User, se
} }
func (service AuthImpl) SendForgotPasswordMail(ctx context.Context, email string) error { func (service AuthImpl) SendForgotPasswordMail(ctx context.Context, email string) error {
tokenStr, err := service.random.String(32) tokenStr, err := service.random.String(ctx, 32)
if err != nil { if err != nil {
return err return err
} }
@@ -353,10 +353,10 @@ func (service AuthImpl) SendForgotPasswordMail(ctx context.Context, email string
var mail strings.Builder var mail strings.Builder
err = mailTemplate.ResetPassword(service.serverSettings.BaseUrl, token.Token).Render(context.Background(), &mail) err = mailTemplate.ResetPassword(service.serverSettings.BaseUrl, token.Token).Render(context.Background(), &mail)
if err != nil { if err != nil {
slog.Error("Could not render reset password email", "err", err) slog.ErrorContext(ctx, "Could not render reset password email", "err", err)
return types.ErrInternal return types.ErrInternal
} }
service.mail.SendMail(email, "Reset Password", mail.String()) service.mail.SendMail(ctx, email, "Reset Password", mail.String())
return nil return nil
} }
@@ -383,7 +383,7 @@ func (service AuthImpl) ForgotPassword(ctx context.Context, tokenStr string, new
user, err := service.db.GetUser(ctx, token.UserId) user, err := service.db.GetUser(ctx, token.UserId)
if err != nil { if err != nil {
slog.Error("Could not get user from token", "err", err) slog.ErrorContext(ctx, "Could not get user from token", "err", err)
return types.ErrInternal return types.ErrInternal
} }
@@ -436,7 +436,7 @@ func (service AuthImpl) GetCsrfToken(ctx context.Context, session *types.Session
return tokens[0].Token, nil return tokens[0].Token, nil
} }
tokenStr, err := service.random.String(32) tokenStr, err := service.random.String(ctx, 32)
if err != nil { if err != nil {
return "", types.ErrInternal return "", types.ErrInternal
} }
@@ -453,7 +453,7 @@ func (service AuthImpl) GetCsrfToken(ctx context.Context, session *types.Session
return "", types.ErrInternal return "", types.ErrInternal
} }
slog.Info("CSRF-Token created", "token", tokenStr) slog.InfoContext(ctx, "CSRF-Token created", "token", tokenStr)
return tokenStr, nil return tokenStr, nil
} }
@@ -473,7 +473,7 @@ func (service AuthImpl) CleanupSessionsAndTokens(ctx context.Context) error {
} }
func (service AuthImpl) createSession(ctx context.Context, userId uuid.UUID) (*types.Session, error) { func (service AuthImpl) createSession(ctx context.Context, userId uuid.UUID) (*types.Session, error) {
sessionId, err := service.random.String(32) sessionId, err := service.random.String(ctx, 32)
if err != nil { if err != nil {
return nil, types.ErrInternal return nil, types.ErrInternal
} }

View File

@@ -36,7 +36,7 @@ func (s Dashboard) Summary(ctx context.Context, user *types.User, month time.Tim
AND error IS NULL AND error IS NULL
AND date(timestamp, 'start of month') = date($2, 'start of month')`, AND date(timestamp, 'start of month') = date($2, 'start of month')`,
user.Id, month) user.Id, month)
err = db.TransformAndLogDbError("dashboard", nil, err) err = db.TransformAndLogDbError(ctx, "dashboard", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -53,7 +53,7 @@ func (s Dashboard) Summary(ctx context.Context, user *types.User, month time.Tim
AND error IS NULL AND error IS NULL
AND date(timestamp, 'start of month') = date($2, 'start of month')`, AND date(timestamp, 'start of month') = date($2, 'start of month')`,
user.Id, month) user.Id, month)
err = db.TransformAndLogDbError("dashboard", nil, err) err = db.TransformAndLogDbError(ctx, "dashboard", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -70,7 +70,7 @@ func (s Dashboard) Summary(ctx context.Context, user *types.User, month time.Tim
AND error IS NULL AND error IS NULL
AND date(timestamp, 'start of month') = date($2, 'start of month')`, AND date(timestamp, 'start of month') = date($2, 'start of month')`,
user.Id, month) user.Id, month)
err = db.TransformAndLogDbError("dashboard", nil, err) err = db.TransformAndLogDbError(ctx, "dashboard", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -86,7 +86,7 @@ func (s Dashboard) Summary(ctx context.Context, user *types.User, month time.Tim
FROM treasure_chest FROM treasure_chest
WHERE user_id = $1`, WHERE user_id = $1`,
user.Id) user.Id)
err = db.TransformAndLogDbError("dashboard", nil, err) err = db.TransformAndLogDbError(ctx, "dashboard", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -99,7 +99,7 @@ func (s Dashboard) Summary(ctx context.Context, user *types.User, month time.Tim
FROM account FROM account
WHERE user_id = $1`, WHERE user_id = $1`,
user.Id) user.Id)
err = db.TransformAndLogDbError("dashboard", nil, err) err = db.TransformAndLogDbError(ctx, "dashboard", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -1,6 +1,7 @@
package service package service
import ( import (
"context"
"fmt" "fmt"
"log/slog" "log/slog"
"net/smtp" "net/smtp"
@@ -9,7 +10,7 @@ import (
type Mail interface { type Mail interface {
// Sending an email is a fire and forget operation. Thus no error handling // Sending an email is a fire and forget operation. Thus no error handling
SendMail(to string, subject string, message string) SendMail(ctx context.Context, to string, subject string, message string)
} }
type MailImpl struct { type MailImpl struct {
@@ -20,11 +21,11 @@ func NewMail(server *types.Settings) MailImpl {
return MailImpl{server: server} return MailImpl{server: server}
} }
func (m MailImpl) SendMail(to string, subject string, message string) { func (m MailImpl) SendMail(ctx context.Context, to string, subject string, message string) {
go m.internalSendMail(to, subject, message) go m.internalSendMail(ctx, to, subject, message)
} }
func (m MailImpl) internalSendMail(to string, subject string, message string) { func (m MailImpl) internalSendMail(ctx context.Context, to string, subject string, message string) {
if m.server.Smtp == nil { if m.server.Smtp == nil {
return return
} }
@@ -47,9 +48,9 @@ func (m MailImpl) internalSendMail(to string, subject string, message string) {
subject, subject,
message) message)
slog.Info("sending mail", "to", to) slog.InfoContext(ctx, "sending mail", "to", to)
err := smtp.SendMail(s.Host+":"+s.Port, auth, s.FromMail, []string{to}, []byte(msg)) err := smtp.SendMail(s.Host+":"+s.Port, auth, s.FromMail, []string{to}, []byte(msg))
if err != nil { if err != nil {
slog.Error("Error sending mail", "err", err) slog.ErrorContext(ctx, "Error sending mail", "err", err)
} }
} }

View File

@@ -1,6 +1,7 @@
package service package service
import ( import (
"context"
"crypto/rand" "crypto/rand"
"encoding/base64" "encoding/base64"
"log/slog" "log/slog"
@@ -10,9 +11,9 @@ import (
) )
type Random interface { type Random interface {
Bytes(size int) ([]byte, error) Bytes(ctx context.Context, size int) ([]byte, error)
String(size int) (string, error) String(ctx context.Context, size int) (string, error)
UUID() (uuid.UUID, error) UUID(ctx context.Context) (uuid.UUID, error)
} }
type RandomImpl struct { type RandomImpl struct {
@@ -22,31 +23,31 @@ func NewRandom() *RandomImpl {
return &RandomImpl{} return &RandomImpl{}
} }
func (r *RandomImpl) Bytes(size int) ([]byte, error) { func (r *RandomImpl) Bytes(ctx context.Context, tsize int) ([]byte, error) {
b := make([]byte, 32) b := make([]byte, 32)
_, err := rand.Read(b) _, err := rand.Read(b)
if err != nil { if err != nil {
slog.Error("Error generating random bytes", "err", err) slog.ErrorContext(ctx, "Error generating random bytes", "err", err)
return []byte{}, types.ErrInternal return []byte{}, types.ErrInternal
} }
return b, nil return b, nil
} }
func (r *RandomImpl) String(size int) (string, error) { func (r *RandomImpl) String(ctx context.Context, size int) (string, error) {
bytes, err := r.Bytes(size) bytes, err := r.Bytes(ctx, size)
if err != nil { if err != nil {
slog.Error("Error generating random string", "err", err) slog.ErrorContext(ctx, "Error generating random string", "err", err)
return "", types.ErrInternal return "", types.ErrInternal
} }
return base64.StdEncoding.EncodeToString(bytes), nil return base64.StdEncoding.EncodeToString(bytes), nil
} }
func (r *RandomImpl) UUID() (uuid.UUID, error) { func (r *RandomImpl) UUID(ctx context.Context) (uuid.UUID, error) {
id, err := uuid.NewRandom() id, err := uuid.NewRandom()
if err != nil { if err != nil {
slog.Error("Error generating random UUID", "err", err) slog.ErrorContext(ctx, "Error generating random UUID", "err", err)
return uuid.Nil, types.ErrInternal return uuid.Nil, types.ErrInternal
} }

View File

@@ -47,7 +47,7 @@ func (s TransactionImpl) Add(ctx context.Context, tx *sqlx.Tx, user *types.User,
if tx == nil { if tx == nil {
ownsTransaction = true ownsTransaction = true
tx, err = s.db.BeginTxx(ctx, nil) tx, err = s.db.BeginTxx(ctx, nil)
err = db.TransformAndLogDbError("transaction Add", nil, err) err = db.TransformAndLogDbError(ctx, "transaction Add", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -66,7 +66,7 @@ func (s TransactionImpl) Add(ctx context.Context, tx *sqlx.Tx, user *types.User,
party, description, error, created_at, created_by) party, description, error, created_at, created_by)
VALUES (:id, :user_id, :account_id, :treasure_chest_id, :value, :timestamp, VALUES (:id, :user_id, :account_id, :treasure_chest_id, :value, :timestamp,
:party, :description, :error, :created_at, :created_by)`, transaction) :party, :description, :error, :created_at, :created_by)`, transaction)
err = db.TransformAndLogDbError("transaction Insert", r, err) err = db.TransformAndLogDbError(ctx, "transaction Insert", r, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -76,7 +76,7 @@ func (s TransactionImpl) Add(ctx context.Context, tx *sqlx.Tx, user *types.User,
UPDATE account UPDATE account
SET current_balance = current_balance + ? SET current_balance = current_balance + ?
WHERE id = ? AND user_id = ?`, transaction.Value, transaction.AccountId, user.Id) WHERE id = ? AND user_id = ?`, transaction.Value, transaction.AccountId, user.Id)
err = db.TransformAndLogDbError("transaction Add", r, err) err = db.TransformAndLogDbError(ctx, "transaction Add", r, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -87,7 +87,7 @@ func (s TransactionImpl) Add(ctx context.Context, tx *sqlx.Tx, user *types.User,
UPDATE treasure_chest UPDATE treasure_chest
SET current_balance = current_balance + ? SET current_balance = current_balance + ?
WHERE id = ? AND user_id = ?`, transaction.Value, transaction.TreasureChestId, user.Id) WHERE id = ? AND user_id = ?`, transaction.Value, transaction.TreasureChestId, user.Id)
err = db.TransformAndLogDbError("transaction Add", r, err) err = db.TransformAndLogDbError(ctx, "transaction Add", r, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -95,7 +95,7 @@ func (s TransactionImpl) Add(ctx context.Context, tx *sqlx.Tx, user *types.User,
if ownsTransaction { if ownsTransaction {
err = tx.Commit() err = tx.Commit()
err = db.TransformAndLogDbError("transaction Add", nil, err) err = db.TransformAndLogDbError(ctx, "transaction Add", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -110,7 +110,7 @@ func (s TransactionImpl) Update(ctx context.Context, user *types.User, input typ
} }
tx, err := s.db.BeginTxx(ctx, nil) tx, err := s.db.BeginTxx(ctx, nil)
err = db.TransformAndLogDbError("transaction Update", nil, err) err = db.TransformAndLogDbError(ctx, "transaction Update", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -120,7 +120,7 @@ func (s TransactionImpl) Update(ctx context.Context, user *types.User, input typ
transaction := &types.Transaction{} transaction := &types.Transaction{}
err = tx.GetContext(ctx, transaction, `SELECT * FROM "transaction" WHERE user_id = ? AND id = ?`, user.Id, input.Id) err = tx.GetContext(ctx, transaction, `SELECT * FROM "transaction" WHERE user_id = ? AND id = ?`, user.Id, input.Id)
err = db.TransformAndLogDbError("transaction Update", nil, err) err = db.TransformAndLogDbError(ctx, "transaction Update", nil, err)
if err != nil { if err != nil {
if errors.Is(err, db.ErrNotFound) { if errors.Is(err, db.ErrNotFound) {
return nil, fmt.Errorf("transaction %v not found: %w", input.Id, ErrBadRequest) return nil, fmt.Errorf("transaction %v not found: %w", input.Id, ErrBadRequest)
@@ -133,7 +133,7 @@ func (s TransactionImpl) Update(ctx context.Context, user *types.User, input typ
UPDATE account UPDATE account
SET current_balance = current_balance - ? SET current_balance = current_balance - ?
WHERE id = ? AND user_id = ?`, transaction.Value, transaction.AccountId, user.Id) WHERE id = ? AND user_id = ?`, transaction.Value, transaction.AccountId, user.Id)
err = db.TransformAndLogDbError("transaction Update", r, err) err = db.TransformAndLogDbError(ctx, "transaction Update", r, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -143,7 +143,7 @@ func (s TransactionImpl) Update(ctx context.Context, user *types.User, input typ
UPDATE treasure_chest UPDATE treasure_chest
SET current_balance = current_balance - ? SET current_balance = current_balance - ?
WHERE id = ? AND user_id = ?`, transaction.Value, transaction.TreasureChestId, user.Id) WHERE id = ? AND user_id = ?`, transaction.Value, transaction.TreasureChestId, user.Id)
err = db.TransformAndLogDbError("transaction Update", r, err) err = db.TransformAndLogDbError(ctx, "transaction Update", r, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -159,7 +159,7 @@ func (s TransactionImpl) Update(ctx context.Context, user *types.User, input typ
UPDATE account UPDATE account
SET current_balance = current_balance + ? SET current_balance = current_balance + ?
WHERE id = ? AND user_id = ?`, transaction.Value, transaction.AccountId, user.Id) WHERE id = ? AND user_id = ?`, transaction.Value, transaction.AccountId, user.Id)
err = db.TransformAndLogDbError("transaction Update", r, err) err = db.TransformAndLogDbError(ctx, "transaction Update", r, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -169,7 +169,7 @@ func (s TransactionImpl) Update(ctx context.Context, user *types.User, input typ
UPDATE treasure_chest UPDATE treasure_chest
SET current_balance = current_balance + ? SET current_balance = current_balance + ?
WHERE id = ? AND user_id = ?`, transaction.Value, transaction.TreasureChestId, user.Id) WHERE id = ? AND user_id = ?`, transaction.Value, transaction.TreasureChestId, user.Id)
err = db.TransformAndLogDbError("transaction Update", r, err) err = db.TransformAndLogDbError(ctx, "transaction Update", r, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -189,13 +189,13 @@ func (s TransactionImpl) Update(ctx context.Context, user *types.User, input typ
updated_by = :updated_by updated_by = :updated_by
WHERE id = :id WHERE id = :id
AND user_id = :user_id`, transaction) AND user_id = :user_id`, transaction)
err = db.TransformAndLogDbError("transaction Update", r, err) err = db.TransformAndLogDbError(ctx, "transaction Update", r, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = tx.Commit() err = tx.Commit()
err = db.TransformAndLogDbError("transaction Update", nil, err) err = db.TransformAndLogDbError(ctx, "transaction Update", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -209,13 +209,13 @@ func (s TransactionImpl) Get(ctx context.Context, user *types.User, id string) (
} }
uuid, err := uuid.Parse(id) uuid, err := uuid.Parse(id)
if err != nil { if err != nil {
slog.Error("transaction get", "err", err) slog.ErrorContext(ctx, "transaction get", "err", err)
return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest) return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest)
} }
var transaction types.Transaction var transaction types.Transaction
err = s.db.GetContext(ctx, &transaction, `SELECT * FROM "transaction" WHERE user_id = ? AND id = ?`, user.Id, uuid) err = s.db.GetContext(ctx, &transaction, `SELECT * FROM "transaction" WHERE user_id = ? AND id = ?`, user.Id, uuid)
err = db.TransformAndLogDbError("transaction Get", nil, err) err = db.TransformAndLogDbError(ctx, "transaction Get", nil, err)
if err != nil { if err != nil {
if errors.Is(err, db.ErrNotFound) { if errors.Is(err, db.ErrNotFound) {
return nil, fmt.Errorf("transaction %v not found: %w", id, ErrBadRequest) return nil, fmt.Errorf("transaction %v not found: %w", id, ErrBadRequest)
@@ -247,7 +247,7 @@ func (s TransactionImpl) GetAll(ctx context.Context, user *types.User, filter ty
filter.AccountId, filter.AccountId, filter.AccountId, filter.AccountId,
filter.TreasureChestId, filter.TreasureChestId, filter.TreasureChestId, filter.TreasureChestId,
filter.Error, filter.Error, filter.Error) filter.Error, filter.Error, filter.Error)
err = db.TransformAndLogDbError("transaction GetAll", nil, err) err = db.TransformAndLogDbError(ctx, "transaction GetAll", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -261,12 +261,12 @@ func (s TransactionImpl) Delete(ctx context.Context, user *types.User, id string
} }
uuid, err := uuid.Parse(id) uuid, err := uuid.Parse(id)
if err != nil { if err != nil {
slog.Error("transaction delete", "err", err) slog.ErrorContext(ctx, "transaction delete", "err", err)
return fmt.Errorf("could not parse Id: %w", ErrBadRequest) return fmt.Errorf("could not parse Id: %w", ErrBadRequest)
} }
tx, err := s.db.BeginTxx(ctx, nil) tx, err := s.db.BeginTxx(ctx, nil)
err = db.TransformAndLogDbError("transaction Delete", nil, err) err = db.TransformAndLogDbError(ctx, "transaction Delete", nil, err)
if err != nil { if err != nil {
return nil return nil
} }
@@ -276,7 +276,7 @@ func (s TransactionImpl) Delete(ctx context.Context, user *types.User, id string
var transaction types.Transaction var transaction types.Transaction
err = tx.GetContext(ctx, &transaction, `SELECT * FROM "transaction" WHERE user_id = ? AND id = ?`, user.Id, uuid) err = tx.GetContext(ctx, &transaction, `SELECT * FROM "transaction" WHERE user_id = ? AND id = ?`, user.Id, uuid)
err = db.TransformAndLogDbError("transaction Delete", nil, err) err = db.TransformAndLogDbError(ctx, "transaction Delete", nil, err)
if err != nil { if err != nil {
return err return err
} }
@@ -287,7 +287,7 @@ func (s TransactionImpl) Delete(ctx context.Context, user *types.User, id string
SET current_balance = current_balance - ? SET current_balance = current_balance - ?
WHERE id = ? WHERE id = ?
AND user_id = ?`, transaction.Value, transaction.AccountId, user.Id) AND user_id = ?`, transaction.Value, transaction.AccountId, user.Id)
err = db.TransformAndLogDbError("transaction Delete", r, err) err = db.TransformAndLogDbError(ctx, "transaction Delete", r, err)
if err != nil && !errors.Is(err, db.ErrNotFound) { if err != nil && !errors.Is(err, db.ErrNotFound) {
return err return err
} }
@@ -299,20 +299,20 @@ func (s TransactionImpl) Delete(ctx context.Context, user *types.User, id string
SET current_balance = current_balance - ? SET current_balance = current_balance - ?
WHERE id = ? WHERE id = ?
AND user_id = ?`, transaction.Value, transaction.TreasureChestId, user.Id) AND user_id = ?`, transaction.Value, transaction.TreasureChestId, user.Id)
err = db.TransformAndLogDbError("transaction Delete", r, err) err = db.TransformAndLogDbError(ctx, "transaction Delete", r, err)
if err != nil && !errors.Is(err, db.ErrNotFound) { if err != nil && !errors.Is(err, db.ErrNotFound) {
return err return err
} }
} }
r, err := tx.ExecContext(ctx, "DELETE FROM \"transaction\" WHERE id = ? AND user_id = ?", uuid, user.Id) r, err := tx.ExecContext(ctx, "DELETE FROM \"transaction\" WHERE id = ? AND user_id = ?", uuid, user.Id)
err = db.TransformAndLogDbError("transaction Delete", r, err) err = db.TransformAndLogDbError(ctx, "transaction Delete", r, err)
if err != nil { if err != nil {
return err return err
} }
err = tx.Commit() err = tx.Commit()
err = db.TransformAndLogDbError("transaction Delete", nil, err) err = db.TransformAndLogDbError(ctx, "transaction Delete", nil, err)
if err != nil { if err != nil {
return err return err
} }
@@ -326,7 +326,7 @@ func (s TransactionImpl) RecalculateBalances(ctx context.Context, user *types.Us
} }
tx, err := s.db.BeginTxx(ctx, nil) tx, err := s.db.BeginTxx(ctx, nil)
err = db.TransformAndLogDbError("transaction RecalculateBalances", nil, err) err = db.TransformAndLogDbError(ctx, "transaction RecalculateBalances", nil, err)
if err != nil { if err != nil {
return err return err
} }
@@ -338,7 +338,7 @@ func (s TransactionImpl) RecalculateBalances(ctx context.Context, user *types.Us
UPDATE account UPDATE account
SET current_balance = 0 SET current_balance = 0
WHERE user_id = ?`, user.Id) WHERE user_id = ?`, user.Id)
err = db.TransformAndLogDbError("transaction RecalculateBalances", r, err) err = db.TransformAndLogDbError(ctx, "transaction RecalculateBalances", r, err)
if err != nil && !errors.Is(err, db.ErrNotFound) { if err != nil && !errors.Is(err, db.ErrNotFound) {
return err return err
} }
@@ -347,7 +347,7 @@ func (s TransactionImpl) RecalculateBalances(ctx context.Context, user *types.Us
UPDATE treasure_chest UPDATE treasure_chest
SET current_balance = 0 SET current_balance = 0
WHERE user_id = ?`, user.Id) WHERE user_id = ?`, user.Id)
err = db.TransformAndLogDbError("transaction RecalculateBalances", r, err) err = db.TransformAndLogDbError(ctx, "transaction RecalculateBalances", r, err)
if err != nil && !errors.Is(err, db.ErrNotFound) { if err != nil && !errors.Is(err, db.ErrNotFound) {
return err return err
} }
@@ -356,21 +356,21 @@ func (s TransactionImpl) RecalculateBalances(ctx context.Context, user *types.Us
SELECT * SELECT *
FROM "transaction" FROM "transaction"
WHERE user_id = ?`, user.Id) WHERE user_id = ?`, user.Id)
err = db.TransformAndLogDbError("transaction RecalculateBalances", nil, err) err = db.TransformAndLogDbError(ctx, "transaction RecalculateBalances", nil, err)
if err != nil && !errors.Is(err, db.ErrNotFound) { if err != nil && !errors.Is(err, db.ErrNotFound) {
return err return err
} }
defer func() { defer func() {
err := rows.Close() err := rows.Close()
if err != nil { if err != nil {
slog.Error("transaction RecalculateBalances", "err", err) slog.ErrorContext(ctx, "transaction RecalculateBalances", "err", err)
} }
}() }()
var transaction types.Transaction var transaction types.Transaction
for rows.Next() { for rows.Next() {
err = rows.StructScan(&transaction) err = rows.StructScan(&transaction)
err = db.TransformAndLogDbError("transaction RecalculateBalances", nil, err) err = db.TransformAndLogDbError(ctx, "transaction RecalculateBalances", nil, err)
if err != nil { if err != nil {
return err return err
} }
@@ -381,7 +381,7 @@ func (s TransactionImpl) RecalculateBalances(ctx context.Context, user *types.Us
SET error = ? SET error = ?
WHERE user_id = ? WHERE user_id = ?
AND id = ?`, transaction.Error, user.Id, transaction.Id) AND id = ?`, transaction.Error, user.Id, transaction.Id)
err = db.TransformAndLogDbError("transaction RecalculateBalances", r, err) err = db.TransformAndLogDbError(ctx, "transaction RecalculateBalances", r, err)
if err != nil { if err != nil {
return err return err
} }
@@ -395,7 +395,7 @@ func (s TransactionImpl) RecalculateBalances(ctx context.Context, user *types.Us
UPDATE account UPDATE account
SET current_balance = current_balance + ? SET current_balance = current_balance + ?
WHERE id = ? AND user_id = ?`, transaction.Value, transaction.AccountId, user.Id) WHERE id = ? AND user_id = ?`, transaction.Value, transaction.AccountId, user.Id)
err = db.TransformAndLogDbError("transaction RecalculateBalances", r, err) err = db.TransformAndLogDbError(ctx, "transaction RecalculateBalances", r, err)
if err != nil { if err != nil {
return err return err
} }
@@ -405,7 +405,7 @@ func (s TransactionImpl) RecalculateBalances(ctx context.Context, user *types.Us
UPDATE treasure_chest UPDATE treasure_chest
SET current_balance = current_balance + ? SET current_balance = current_balance + ?
WHERE id = ? AND user_id = ?`, transaction.Value, transaction.TreasureChestId, user.Id) WHERE id = ? AND user_id = ?`, transaction.Value, transaction.TreasureChestId, user.Id)
err = db.TransformAndLogDbError("transaction RecalculateBalances", r, err) err = db.TransformAndLogDbError(ctx, "transaction RecalculateBalances", r, err)
if err != nil { if err != nil {
return err return err
} }
@@ -413,7 +413,7 @@ func (s TransactionImpl) RecalculateBalances(ctx context.Context, user *types.Us
} }
err = tx.Commit() err = tx.Commit()
err = db.TransformAndLogDbError("transaction RecalculateBalances", nil, err) err = db.TransformAndLogDbError(ctx, "transaction RecalculateBalances", nil, err)
if err != nil { if err != nil {
return err return err
} }
@@ -434,7 +434,7 @@ func (s TransactionImpl) validateAndEnrichTransaction(ctx context.Context, tx *s
) )
if oldTransaction == nil { if oldTransaction == nil {
id, err = s.random.UUID() id, err = s.random.UUID(ctx)
if err != nil { if err != nil {
return nil, types.ErrInternal return nil, types.ErrInternal
} }
@@ -451,12 +451,12 @@ func (s TransactionImpl) validateAndEnrichTransaction(ctx context.Context, tx *s
if input.AccountId != nil { if input.AccountId != nil {
err = tx.GetContext(ctx, &rowCount, `SELECT COUNT(*) FROM account WHERE id = ? AND user_id = ?`, input.AccountId, userId) err = tx.GetContext(ctx, &rowCount, `SELECT COUNT(*) FROM account WHERE id = ? AND user_id = ?`, input.AccountId, userId)
err = db.TransformAndLogDbError("transaction validate", nil, err) err = db.TransformAndLogDbError(ctx, "transaction validate", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if rowCount == 0 { if rowCount == 0 {
slog.Error("transaction validate", "err", err) slog.ErrorContext(ctx, "transaction validate", "err", err)
return nil, fmt.Errorf("account not found: %w", ErrBadRequest) return nil, fmt.Errorf("account not found: %w", ErrBadRequest)
} }
} }
@@ -464,7 +464,7 @@ func (s TransactionImpl) validateAndEnrichTransaction(ctx context.Context, tx *s
if input.TreasureChestId != nil { if input.TreasureChestId != nil {
var treasureChest types.TreasureChest var treasureChest types.TreasureChest
err = tx.GetContext(ctx, &treasureChest, `SELECT * FROM treasure_chest WHERE id = ? AND user_id = ?`, input.TreasureChestId, userId) err = tx.GetContext(ctx, &treasureChest, `SELECT * FROM treasure_chest WHERE id = ? AND user_id = ?`, input.TreasureChestId, userId)
err = db.TransformAndLogDbError("transaction validate", nil, err) err = db.TransformAndLogDbError(ctx, "transaction validate", nil, err)
if err != nil { if err != nil {
if errors.Is(err, db.ErrNotFound) { if errors.Is(err, db.ErrNotFound) {
return nil, fmt.Errorf("treasure chest not found: %w", ErrBadRequest) return nil, fmt.Errorf("treasure chest not found: %w", ErrBadRequest)

View File

@@ -51,7 +51,7 @@ func (s TransactionRecurringImpl) Add(ctx context.Context,
} }
tx, err := s.db.BeginTxx(ctx, nil) tx, err := s.db.BeginTxx(ctx, nil)
err = db.TransformAndLogDbError("transactionRecurring Add", nil, err) err = db.TransformAndLogDbError(ctx, "transactionRecurring Add", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -70,13 +70,13 @@ func (s TransactionRecurringImpl) Add(ctx context.Context,
VALUES (:id, :user_id, :interval_months, VALUES (:id, :user_id, :interval_months,
:next_execution, :party, :description, :account_id, :treasure_chest_id, :value, :created_at, :created_by)`, :next_execution, :party, :description, :account_id, :treasure_chest_id, :value, :created_at, :created_by)`,
transactionRecurring) transactionRecurring)
err = db.TransformAndLogDbError("transactionRecurring Insert", r, err) err = db.TransformAndLogDbError(ctx, "transactionRecurring Insert", r, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = tx.Commit() err = tx.Commit()
err = db.TransformAndLogDbError("transactionRecurring Add", nil, err) err = db.TransformAndLogDbError(ctx, "transactionRecurring Add", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -93,12 +93,12 @@ func (s TransactionRecurringImpl) Update(ctx context.Context,
} }
uuid, err := uuid.Parse(input.Id) uuid, err := uuid.Parse(input.Id)
if err != nil { if err != nil {
slog.Error("transactionRecurring update", "err", err) slog.ErrorContext(ctx, "transactionRecurring update", "err", err)
return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest) return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest)
} }
tx, err := s.db.BeginTxx(ctx, nil) tx, err := s.db.BeginTxx(ctx, nil)
err = db.TransformAndLogDbError("transactionRecurring Update", nil, err) err = db.TransformAndLogDbError(ctx, "transactionRecurring Update", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -108,7 +108,7 @@ func (s TransactionRecurringImpl) Update(ctx context.Context,
transactionRecurring := &types.TransactionRecurring{} transactionRecurring := &types.TransactionRecurring{}
err = tx.GetContext(ctx, transactionRecurring, `SELECT * FROM transaction_recurring WHERE user_id = ? AND id = ?`, user.Id, uuid) err = tx.GetContext(ctx, transactionRecurring, `SELECT * FROM transaction_recurring WHERE user_id = ? AND id = ?`, user.Id, uuid)
err = db.TransformAndLogDbError("transactionRecurring Update", nil, err) err = db.TransformAndLogDbError(ctx, "transactionRecurring Update", nil, err)
if err != nil { if err != nil {
if errors.Is(err, db.ErrNotFound) { if errors.Is(err, db.ErrNotFound) {
return nil, fmt.Errorf("transactionRecurring %v not found: %w", input.Id, ErrBadRequest) return nil, fmt.Errorf("transactionRecurring %v not found: %w", input.Id, ErrBadRequest)
@@ -135,13 +135,13 @@ func (s TransactionRecurringImpl) Update(ctx context.Context,
updated_by = :updated_by updated_by = :updated_by
WHERE id = :id WHERE id = :id
AND user_id = :user_id`, transactionRecurring) AND user_id = :user_id`, transactionRecurring)
err = db.TransformAndLogDbError("transactionRecurring Update", r, err) err = db.TransformAndLogDbError(ctx, "transactionRecurring Update", r, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = tx.Commit() err = tx.Commit()
err = db.TransformAndLogDbError("transactionRecurring Update", nil, err) err = db.TransformAndLogDbError(ctx, "transactionRecurring Update", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -161,7 +161,7 @@ func (s TransactionRecurringImpl) GetAll(ctx context.Context, user *types.User)
WHERE user_id = ? WHERE user_id = ?
ORDER BY created_at DESC`, ORDER BY created_at DESC`,
user.Id) user.Id)
err = db.TransformAndLogDbError("transactionRecurring GetAll", nil, err) err = db.TransformAndLogDbError(ctx, "transactionRecurring GetAll", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -176,12 +176,12 @@ func (s TransactionRecurringImpl) GetAllByAccount(ctx context.Context, user *typ
accountUuid, err := uuid.Parse(accountId) accountUuid, err := uuid.Parse(accountId)
if err != nil { if err != nil {
slog.Error("transactionRecurring GetAllByAccount", "err", err) slog.ErrorContext(ctx, "transactionRecurring GetAllByAccount", "err", err)
return nil, fmt.Errorf("could not parse accountId: %w", ErrBadRequest) return nil, fmt.Errorf("could not parse accountId: %w", ErrBadRequest)
} }
tx, err := s.db.BeginTxx(ctx, nil) tx, err := s.db.BeginTxx(ctx, nil)
err = db.TransformAndLogDbError("transactionRecurring GetAllByAccount", nil, err) err = db.TransformAndLogDbError(ctx, "transactionRecurring GetAllByAccount", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -191,7 +191,7 @@ func (s TransactionRecurringImpl) GetAllByAccount(ctx context.Context, user *typ
var rowCount int var rowCount int
err = tx.GetContext(ctx, &rowCount, `SELECT COUNT(*) FROM account WHERE id = ? AND user_id = ?`, accountUuid, user.Id) err = tx.GetContext(ctx, &rowCount, `SELECT COUNT(*) FROM account WHERE id = ? AND user_id = ?`, accountUuid, user.Id)
err = db.TransformAndLogDbError("transactionRecurring GetAllByAccount", nil, err) err = db.TransformAndLogDbError(ctx, "transactionRecurring GetAllByAccount", nil, err)
if err != nil { if err != nil {
if errors.Is(err, db.ErrNotFound) { if errors.Is(err, db.ErrNotFound) {
return nil, fmt.Errorf("account %v not found: %w", accountId, ErrBadRequest) return nil, fmt.Errorf("account %v not found: %w", accountId, ErrBadRequest)
@@ -207,13 +207,13 @@ func (s TransactionRecurringImpl) GetAllByAccount(ctx context.Context, user *typ
AND account_id = ? AND account_id = ?
ORDER BY created_at DESC`, ORDER BY created_at DESC`,
user.Id, accountUuid) user.Id, accountUuid)
err = db.TransformAndLogDbError("transactionRecurring GetAll", nil, err) err = db.TransformAndLogDbError(ctx, "transactionRecurring GetAll", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = tx.Commit() err = tx.Commit()
err = db.TransformAndLogDbError("transactionRecurring GetAllByAccount", nil, err) err = db.TransformAndLogDbError(ctx, "transactionRecurring GetAllByAccount", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -231,12 +231,12 @@ func (s TransactionRecurringImpl) GetAllByTreasureChest(ctx context.Context,
treasureChestUuid, err := uuid.Parse(treasureChestId) treasureChestUuid, err := uuid.Parse(treasureChestId)
if err != nil { if err != nil {
slog.Error("transactionRecurring GetAllByTreasureChest", "err", err) slog.ErrorContext(ctx, "transactionRecurring GetAllByTreasureChest", "err", err)
return nil, fmt.Errorf("could not parse treasureChestId: %w", ErrBadRequest) return nil, fmt.Errorf("could not parse treasureChestId: %w", ErrBadRequest)
} }
tx, err := s.db.BeginTxx(ctx, nil) tx, err := s.db.BeginTxx(ctx, nil)
err = db.TransformAndLogDbError("transactionRecurring GetAllByTreasureChest", nil, err) err = db.TransformAndLogDbError(ctx, "transactionRecurring GetAllByTreasureChest", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -246,7 +246,7 @@ func (s TransactionRecurringImpl) GetAllByTreasureChest(ctx context.Context,
var rowCount int var rowCount int
err = tx.GetContext(ctx, &rowCount, `SELECT COUNT(*) FROM treasure_chest WHERE id = ? AND user_id = ?`, treasureChestId, user.Id) err = tx.GetContext(ctx, &rowCount, `SELECT COUNT(*) FROM treasure_chest WHERE id = ? AND user_id = ?`, treasureChestId, user.Id)
err = db.TransformAndLogDbError("transactionRecurring GetAllByTreasureChest", nil, err) err = db.TransformAndLogDbError(ctx, "transactionRecurring GetAllByTreasureChest", nil, err)
if err != nil { if err != nil {
if errors.Is(err, db.ErrNotFound) { if errors.Is(err, db.ErrNotFound) {
return nil, fmt.Errorf("treasurechest %v not found: %w", treasureChestId, ErrBadRequest) return nil, fmt.Errorf("treasurechest %v not found: %w", treasureChestId, ErrBadRequest)
@@ -262,13 +262,13 @@ func (s TransactionRecurringImpl) GetAllByTreasureChest(ctx context.Context,
AND treasure_chest_id = ? AND treasure_chest_id = ?
ORDER BY created_at DESC`, ORDER BY created_at DESC`,
user.Id, treasureChestUuid) user.Id, treasureChestUuid)
err = db.TransformAndLogDbError("transactionRecurring GetAll", nil, err) err = db.TransformAndLogDbError(ctx, "transactionRecurring GetAll", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = tx.Commit() err = tx.Commit()
err = db.TransformAndLogDbError("transactionRecurring GetAllByTreasureChest", nil, err) err = db.TransformAndLogDbError(ctx, "transactionRecurring GetAllByTreasureChest", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -282,12 +282,12 @@ func (s TransactionRecurringImpl) Delete(ctx context.Context, user *types.User,
} }
uuid, err := uuid.Parse(id) uuid, err := uuid.Parse(id)
if err != nil { if err != nil {
slog.Error("transactionRecurring delete", "err", err) slog.ErrorContext(ctx, "transactionRecurring delete", "err", err)
return fmt.Errorf("could not parse Id: %w", ErrBadRequest) return fmt.Errorf("could not parse Id: %w", ErrBadRequest)
} }
tx, err := s.db.BeginTxx(ctx, nil) tx, err := s.db.BeginTxx(ctx, nil)
err = db.TransformAndLogDbError("transactionRecurring Delete", nil, err) err = db.TransformAndLogDbError(ctx, "transactionRecurring Delete", nil, err)
if err != nil { if err != nil {
return nil return nil
} }
@@ -297,19 +297,19 @@ func (s TransactionRecurringImpl) Delete(ctx context.Context, user *types.User,
var transactionRecurring types.TransactionRecurring var transactionRecurring types.TransactionRecurring
err = tx.GetContext(ctx, &transactionRecurring, `SELECT * FROM transaction_recurring WHERE user_id = ? AND id = ?`, user.Id, uuid) err = tx.GetContext(ctx, &transactionRecurring, `SELECT * FROM transaction_recurring WHERE user_id = ? AND id = ?`, user.Id, uuid)
err = db.TransformAndLogDbError("transactionRecurring Delete", nil, err) err = db.TransformAndLogDbError(ctx, "transactionRecurring Delete", nil, err)
if err != nil { if err != nil {
return err return err
} }
r, err := tx.ExecContext(ctx, "DELETE FROM transaction_recurring WHERE id = ? AND user_id = ?", uuid, user.Id) r, err := tx.ExecContext(ctx, "DELETE FROM transaction_recurring WHERE id = ? AND user_id = ?", uuid, user.Id)
err = db.TransformAndLogDbError("transactionRecurring Delete", r, err) err = db.TransformAndLogDbError(ctx, "transactionRecurring Delete", r, err)
if err != nil { if err != nil {
return err return err
} }
err = tx.Commit() err = tx.Commit()
err = db.TransformAndLogDbError("transactionRecurring Delete", nil, err) err = db.TransformAndLogDbError(ctx, "transactionRecurring Delete", nil, err)
if err != nil { if err != nil {
return err return err
} }
@@ -321,7 +321,7 @@ func (s TransactionRecurringImpl) GenerateTransactions(ctx context.Context) erro
now := s.clock.Now() now := s.clock.Now()
tx, err := s.db.BeginTxx(ctx, nil) tx, err := s.db.BeginTxx(ctx, nil)
err = db.TransformAndLogDbError("transactionRecurring GenerateTransactions", nil, err) err = db.TransformAndLogDbError(ctx, "transactionRecurring GenerateTransactions", nil, err)
if err != nil { if err != nil {
return err return err
} }
@@ -333,7 +333,7 @@ func (s TransactionRecurringImpl) GenerateTransactions(ctx context.Context) erro
err = tx.SelectContext(ctx, &recurringTransactions, ` err = tx.SelectContext(ctx, &recurringTransactions, `
SELECT * FROM transaction_recurring WHERE next_execution <= ?`, SELECT * FROM transaction_recurring WHERE next_execution <= ?`,
now) now)
err = db.TransformAndLogDbError("transactionRecurring GenerateTransactions", nil, err) err = db.TransformAndLogDbError(ctx, "transactionRecurring GenerateTransactions", nil, err)
if err != nil { if err != nil {
return err return err
} }
@@ -359,14 +359,14 @@ func (s TransactionRecurringImpl) GenerateTransactions(ctx context.Context) erro
nextExecution := transactionRecurring.NextExecution.AddDate(0, int(transactionRecurring.IntervalMonths), 0) 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 = ?`, r, err := tx.ExecContext(ctx, `UPDATE transaction_recurring SET next_execution = ? WHERE id = ? AND user_id = ?`,
nextExecution, transactionRecurring.Id, user.Id) nextExecution, transactionRecurring.Id, user.Id)
err = db.TransformAndLogDbError("transactionRecurring GenerateTransactions", r, err) err = db.TransformAndLogDbError(ctx, "transactionRecurring GenerateTransactions", r, err)
if err != nil { if err != nil {
return err return err
} }
} }
err = tx.Commit() err = tx.Commit()
err = db.TransformAndLogDbError("transactionRecurring GenerateTransactions", nil, err) err = db.TransformAndLogDbError(ctx, "transactionRecurring GenerateTransactions", nil, err)
if err != nil { if err != nil {
return err return err
} }
@@ -395,7 +395,7 @@ func (s TransactionRecurringImpl) validateAndEnrichTransactionRecurring(
) )
if oldTransactionRecurring == nil { if oldTransactionRecurring == nil {
id, err = s.random.UUID() id, err = s.random.UUID(ctx)
if err != nil { if err != nil {
return nil, types.ErrInternal return nil, types.ErrInternal
} }
@@ -415,17 +415,17 @@ func (s TransactionRecurringImpl) validateAndEnrichTransactionRecurring(
if input.AccountId != "" { if input.AccountId != "" {
temp, err := uuid.Parse(input.AccountId) temp, err := uuid.Parse(input.AccountId)
if err != nil { if err != nil {
slog.Error("transactionRecurring validate", "err", err) slog.ErrorContext(ctx, "transactionRecurring validate", "err", err)
return nil, fmt.Errorf("could not parse accountId: %w", ErrBadRequest) return nil, fmt.Errorf("could not parse accountId: %w", ErrBadRequest)
} }
accountUuid = &temp accountUuid = &temp
err = tx.GetContext(ctx, &rowCount, `SELECT COUNT(*) FROM account WHERE id = ? AND user_id = ?`, accountUuid, userId) err = tx.GetContext(ctx, &rowCount, `SELECT COUNT(*) FROM account WHERE id = ? AND user_id = ?`, accountUuid, userId)
err = db.TransformAndLogDbError("transactionRecurring validate", nil, err) err = db.TransformAndLogDbError(ctx, "transactionRecurring validate", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if rowCount == 0 { if rowCount == 0 {
slog.Error("transactionRecurring validate", "err", err) slog.ErrorContext(ctx, "transactionRecurring validate", "err", err)
return nil, fmt.Errorf("account not found: %w", ErrBadRequest) return nil, fmt.Errorf("account not found: %w", ErrBadRequest)
} }
@@ -435,13 +435,13 @@ func (s TransactionRecurringImpl) validateAndEnrichTransactionRecurring(
if input.TreasureChestId != "" { if input.TreasureChestId != "" {
temp, err := uuid.Parse(input.TreasureChestId) temp, err := uuid.Parse(input.TreasureChestId)
if err != nil { if err != nil {
slog.Error("transactionRecurring validate", "err", err) slog.ErrorContext(ctx, "transactionRecurring validate", "err", err)
return nil, fmt.Errorf("could not parse treasureChestId: %w", ErrBadRequest) return nil, fmt.Errorf("could not parse treasureChestId: %w", ErrBadRequest)
} }
treasureChestUuid = &temp treasureChestUuid = &temp
var treasureChest types.TreasureChest var treasureChest types.TreasureChest
err = tx.GetContext(ctx, &treasureChest, `SELECT * FROM treasure_chest WHERE id = ? AND user_id = ?`, treasureChestUuid, userId) err = tx.GetContext(ctx, &treasureChest, `SELECT * FROM treasure_chest WHERE id = ? AND user_id = ?`, treasureChestUuid, userId)
err = db.TransformAndLogDbError("transactionRecurring validate", nil, err) err = db.TransformAndLogDbError(ctx, "transactionRecurring validate", nil, err)
if err != nil { if err != nil {
if errors.Is(err, db.ErrNotFound) { if errors.Is(err, db.ErrNotFound) {
return nil, fmt.Errorf("treasure chest not found: %w", ErrBadRequest) return nil, fmt.Errorf("treasure chest not found: %w", ErrBadRequest)
@@ -455,17 +455,17 @@ func (s TransactionRecurringImpl) validateAndEnrichTransactionRecurring(
} }
if !hasAccount && !hasTreasureChest { if !hasAccount && !hasTreasureChest {
slog.Error("transactionRecurring validate", "err", err) slog.ErrorContext(ctx, "transactionRecurring validate", "err", err)
return nil, fmt.Errorf("either account or treasure chest is required: %w", ErrBadRequest) return nil, fmt.Errorf("either account or treasure chest is required: %w", ErrBadRequest)
} }
if hasAccount && hasTreasureChest { if hasAccount && hasTreasureChest {
slog.Error("transactionRecurring validate", "err", err) slog.ErrorContext(ctx, "transactionRecurring validate", "err", err)
return nil, fmt.Errorf("either account or treasure chest is required, not both: %w", ErrBadRequest) return nil, fmt.Errorf("either account or treasure chest is required, not both: %w", ErrBadRequest)
} }
valueFloat, err := strconv.ParseFloat(input.Value, 64) valueFloat, err := strconv.ParseFloat(input.Value, 64)
if err != nil { if err != nil {
slog.Error("transactionRecurring validate", "err", err) slog.ErrorContext(ctx, "transactionRecurring validate", "err", err)
return nil, fmt.Errorf("could not parse value: %w", ErrBadRequest) return nil, fmt.Errorf("could not parse value: %w", ErrBadRequest)
} }
value := int64(math.Round(valueFloat * DECIMALS_MULTIPLIER)) value := int64(math.Round(valueFloat * DECIMALS_MULTIPLIER))
@@ -484,18 +484,18 @@ func (s TransactionRecurringImpl) validateAndEnrichTransactionRecurring(
} }
intervalMonths, err = strconv.ParseInt(input.IntervalMonths, 10, 0) intervalMonths, err = strconv.ParseInt(input.IntervalMonths, 10, 0)
if err != nil { if err != nil {
slog.Error("transactionRecurring validate", "err", err) slog.ErrorContext(ctx, "transactionRecurring validate", "err", err)
return nil, fmt.Errorf("could not parse intervalMonths: %w", ErrBadRequest) return nil, fmt.Errorf("could not parse intervalMonths: %w", ErrBadRequest)
} }
if intervalMonths < 1 { if intervalMonths < 1 {
slog.Error("transactionRecurring validate", "err", err) slog.ErrorContext(ctx, "transactionRecurring validate", "err", err)
return nil, fmt.Errorf("intervalMonths needs to be greater than 0: %w", ErrBadRequest) return nil, fmt.Errorf("intervalMonths needs to be greater than 0: %w", ErrBadRequest)
} }
var nextExecution *time.Time = nil var nextExecution *time.Time = nil
if input.NextExecution != "" { if input.NextExecution != "" {
t, err := time.Parse("2006-01-02", input.NextExecution) t, err := time.Parse("2006-01-02", input.NextExecution)
if err != nil { if err != nil {
slog.Error("transaction validate", "err", err) slog.ErrorContext(ctx, "transaction validate", "err", err)
return nil, fmt.Errorf("could not parse timestamp: %w", ErrBadRequest) return nil, fmt.Errorf("could not parse timestamp: %w", ErrBadRequest)
} }

View File

@@ -40,7 +40,7 @@ func (s TreasureChestImpl) Add(ctx context.Context, user *types.User, parentId,
return nil, ErrUnauthorized return nil, ErrUnauthorized
} }
newId, err := s.random.UUID() newId, err := s.random.UUID(ctx)
if err != nil { if err != nil {
return nil, types.ErrInternal return nil, types.ErrInternal
} }
@@ -80,7 +80,7 @@ func (s TreasureChestImpl) Add(ctx context.Context, user *types.User, parentId,
r, err := s.db.NamedExecContext(ctx, ` r, err := s.db.NamedExecContext(ctx, `
INSERT INTO treasure_chest (id, parent_id, user_id, name, current_balance, created_at, created_by) INSERT INTO treasure_chest (id, parent_id, user_id, name, current_balance, created_at, created_by)
VALUES (:id, :parent_id, :user_id, :name, :current_balance, :created_at, :created_by)`, treasureChest) VALUES (:id, :parent_id, :user_id, :name, :current_balance, :created_at, :created_by)`, treasureChest)
err = db.TransformAndLogDbError("treasureChest Insert", r, err) err = db.TransformAndLogDbError(ctx, "treasureChest Insert", r, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -98,12 +98,12 @@ func (s TreasureChestImpl) Update(ctx context.Context, user *types.User, idStr,
} }
id, err := uuid.Parse(idStr) id, err := uuid.Parse(idStr)
if err != nil { if err != nil {
slog.Error("treasureChest update", "err", err) slog.ErrorContext(ctx, "treasureChest update", "err", err)
return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest) return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest)
} }
tx, err := s.db.BeginTxx(ctx, nil) tx, err := s.db.BeginTxx(ctx, nil)
err = db.TransformAndLogDbError("treasureChest Update", nil, err) err = db.TransformAndLogDbError(ctx, "treasureChest Update", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -113,7 +113,7 @@ func (s TreasureChestImpl) Update(ctx context.Context, user *types.User, idStr,
treasureChest := &types.TreasureChest{} treasureChest := &types.TreasureChest{}
err = tx.GetContext(ctx, treasureChest, `SELECT * FROM treasure_chest WHERE user_id = ? AND id = ?`, user.Id, id) err = tx.GetContext(ctx, treasureChest, `SELECT * FROM treasure_chest WHERE user_id = ? AND id = ?`, user.Id, id)
err = db.TransformAndLogDbError("treasureChest Update", nil, err) err = db.TransformAndLogDbError(ctx, "treasureChest Update", nil, err)
if err != nil { if err != nil {
if errors.Is(err, db.ErrNotFound) { if errors.Is(err, db.ErrNotFound) {
return nil, fmt.Errorf("treasureChest %v not found: %w", idStr, err) return nil, fmt.Errorf("treasureChest %v not found: %w", idStr, err)
@@ -129,7 +129,7 @@ func (s TreasureChestImpl) Update(ctx context.Context, user *types.User, idStr,
} }
var childCount int var childCount int
err = tx.GetContext(ctx, &childCount, `SELECT COUNT(*) FROM treasure_chest WHERE user_id = ? AND parent_id = ?`, user.Id, id) err = tx.GetContext(ctx, &childCount, `SELECT COUNT(*) FROM treasure_chest WHERE user_id = ? AND parent_id = ?`, user.Id, id)
err = db.TransformAndLogDbError("treasureChest Update", nil, err) err = db.TransformAndLogDbError(ctx, "treasureChest Update", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -156,13 +156,13 @@ func (s TreasureChestImpl) Update(ctx context.Context, user *types.User, idStr,
updated_by = :updated_by updated_by = :updated_by
WHERE id = :id WHERE id = :id
AND user_id = :user_id`, treasureChest) AND user_id = :user_id`, treasureChest)
err = db.TransformAndLogDbError("treasureChest Update", r, err) err = db.TransformAndLogDbError(ctx, "treasureChest Update", r, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = tx.Commit() err = tx.Commit()
err = db.TransformAndLogDbError("treasureChest Update", nil, err) err = db.TransformAndLogDbError(ctx, "treasureChest Update", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -176,13 +176,13 @@ func (s TreasureChestImpl) Get(ctx context.Context, user *types.User, id string)
} }
uuid, err := uuid.Parse(id) uuid, err := uuid.Parse(id)
if err != nil { if err != nil {
slog.Error("treasureChest get", "err", err) slog.ErrorContext(ctx, "treasureChest get", "err", err)
return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest) return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest)
} }
var treasureChest types.TreasureChest var treasureChest types.TreasureChest
err = s.db.GetContext(ctx, &treasureChest, `SELECT * FROM treasure_chest WHERE user_id = ? AND id = ?`, user.Id, uuid) err = s.db.GetContext(ctx, &treasureChest, `SELECT * FROM treasure_chest WHERE user_id = ? AND id = ?`, user.Id, uuid)
err = db.TransformAndLogDbError("treasureChest Get", nil, err) err = db.TransformAndLogDbError(ctx, "treasureChest Get", nil, err)
if err != nil { if err != nil {
if errors.Is(err, db.ErrNotFound) { if errors.Is(err, db.ErrNotFound) {
return nil, fmt.Errorf("treasureChest %v not found: %w", id, err) return nil, fmt.Errorf("treasureChest %v not found: %w", id, err)
@@ -200,7 +200,7 @@ func (s TreasureChestImpl) GetAll(ctx context.Context, user *types.User) ([]*typ
treasureChests := make([]*types.TreasureChest, 0) treasureChests := make([]*types.TreasureChest, 0)
err := s.db.SelectContext(ctx, &treasureChests, `SELECT * FROM treasure_chest WHERE user_id = ?`, user.Id) err := s.db.SelectContext(ctx, &treasureChests, `SELECT * FROM treasure_chest WHERE user_id = ?`, user.Id)
err = db.TransformAndLogDbError("treasureChest GetAll", nil, err) err = db.TransformAndLogDbError(ctx, "treasureChest GetAll", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -214,12 +214,12 @@ func (s TreasureChestImpl) Delete(ctx context.Context, user *types.User, idStr s
} }
id, err := uuid.Parse(idStr) id, err := uuid.Parse(idStr)
if err != nil { if err != nil {
slog.Error("treasureChest delete", "err", err) slog.ErrorContext(ctx, "treasureChest delete", "err", err)
return fmt.Errorf("could not parse Id: %w", ErrBadRequest) return fmt.Errorf("could not parse Id: %w", ErrBadRequest)
} }
tx, err := s.db.BeginTxx(ctx, nil) tx, err := s.db.BeginTxx(ctx, nil)
err = db.TransformAndLogDbError("treasureChest Delete", nil, err) err = db.TransformAndLogDbError(ctx, "treasureChest Delete", nil, err)
if err != nil { if err != nil {
return nil return nil
} }
@@ -229,7 +229,7 @@ func (s TreasureChestImpl) Delete(ctx context.Context, user *types.User, idStr s
childCount := 0 childCount := 0
err = tx.GetContext(ctx, &childCount, `SELECT COUNT(*) FROM treasure_chest WHERE user_id = ? AND parent_id = ?`, user.Id, id) err = tx.GetContext(ctx, &childCount, `SELECT COUNT(*) FROM treasure_chest WHERE user_id = ? AND parent_id = ?`, user.Id, id)
err = db.TransformAndLogDbError("treasureChest Delete", nil, err) err = db.TransformAndLogDbError(ctx, "treasureChest Delete", nil, err)
if err != nil { if err != nil {
return err return err
} }
@@ -242,7 +242,7 @@ func (s TreasureChestImpl) Delete(ctx context.Context, user *types.User, idStr s
err = tx.GetContext(ctx, &transactionsCount, err = tx.GetContext(ctx, &transactionsCount,
`SELECT COUNT(*) FROM "transaction" WHERE user_id = ? AND treasure_chest_id = ?`, `SELECT COUNT(*) FROM "transaction" WHERE user_id = ? AND treasure_chest_id = ?`,
user.Id, id) user.Id, id)
err = db.TransformAndLogDbError("treasureChest Delete", nil, err) err = db.TransformAndLogDbError(ctx, "treasureChest Delete", nil, err)
if err != nil { if err != nil {
return err return err
} }
@@ -254,7 +254,7 @@ func (s TreasureChestImpl) Delete(ctx context.Context, user *types.User, idStr s
err = tx.GetContext(ctx, &recurringCount, ` err = tx.GetContext(ctx, &recurringCount, `
SELECT COUNT(*) FROM transaction_recurring WHERE user_id = ? AND treasure_chest_id = ?`, SELECT COUNT(*) FROM transaction_recurring WHERE user_id = ? AND treasure_chest_id = ?`,
user.Id, id) user.Id, id)
err = db.TransformAndLogDbError("treasureChest Delete", nil, err) err = db.TransformAndLogDbError(ctx, "treasureChest Delete", nil, err)
if err != nil { if err != nil {
return err return err
} }
@@ -263,13 +263,13 @@ func (s TreasureChestImpl) Delete(ctx context.Context, user *types.User, idStr s
} }
r, err := tx.ExecContext(ctx, `DELETE FROM treasure_chest WHERE id = ? AND user_id = ?`, id, user.Id) r, err := tx.ExecContext(ctx, `DELETE FROM treasure_chest WHERE id = ? AND user_id = ?`, id, user.Id)
err = db.TransformAndLogDbError("treasureChest Delete", r, err) err = db.TransformAndLogDbError(ctx, "treasureChest Delete", r, err)
if err != nil { if err != nil {
return err return err
} }
err = tx.Commit() err = tx.Commit()
err = db.TransformAndLogDbError("treasureChest Delete", nil, err) err = db.TransformAndLogDbError(ctx, "treasureChest Delete", nil, err)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -1,6 +1,7 @@
package types package types
import ( import (
"context"
"errors" "errors"
"log/slog" "log/slog"
) )
@@ -26,13 +27,13 @@ type SmtpSettings struct {
FromName string FromName string
} }
func NewSettingsFromEnv(env func(string) string) (*Settings, error) { func NewSettingsFromEnv(ctx context.Context, env func(string) string) (*Settings, error) {
var ( var (
smtp *SmtpSettings smtp *SmtpSettings
err error err error
) )
if env("SMTP_ENABLED") == "true" { if env("SMTP_ENABLED") == "true" {
smtp, err = getSmtpSettings(env) smtp, err = getSmtpSettings(ctx, env)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -46,26 +47,26 @@ func NewSettingsFromEnv(env func(string) string) (*Settings, error) {
} }
if settings.BaseUrl == "" { if settings.BaseUrl == "" {
slog.Error("BASE_URL must be set") slog.ErrorContext(ctx, "BASE_URL must be set")
return nil, ErrMissingConfig return nil, ErrMissingConfig
} }
if settings.Port == "" { if settings.Port == "" {
slog.Error("PORT must be set") slog.ErrorContext(ctx, "PORT must be set")
return nil, ErrMissingConfig return nil, ErrMissingConfig
} }
if settings.Environment == "" { if settings.Environment == "" {
slog.Error("ENVIRONMENT must be set") slog.ErrorContext(ctx, "ENVIRONMENT must be set")
return nil, ErrMissingConfig return nil, ErrMissingConfig
} }
slog.Info("settings read", "BASE_URL", settings.BaseUrl) slog.InfoContext(ctx, "settings read", "BASE_URL", settings.BaseUrl)
slog.Info("settings read", "ENVIRONMENT", settings.Environment) slog.InfoContext(ctx, "settings read", "ENVIRONMENT", settings.Environment)
slog.Info("settings read", "ENVIRONMENT", settings.Environment) slog.InfoContext(ctx, "settings read", "ENVIRONMENT", settings.Environment)
return settings, nil return settings, nil
} }
func getSmtpSettings(env func(string) string) (*SmtpSettings, error) { func getSmtpSettings(ctx context.Context, env func(string) string) (*SmtpSettings, error) {
smtp := SmtpSettings{ smtp := SmtpSettings{
Host: env("SMTP_HOST"), Host: env("SMTP_HOST"),
Port: env("SMTP_PORT"), Port: env("SMTP_PORT"),
@@ -76,27 +77,27 @@ func getSmtpSettings(env func(string) string) (*SmtpSettings, error) {
} }
if smtp.Host == "" { if smtp.Host == "" {
slog.Error("SMTP_HOST must be set") slog.ErrorContext(ctx, "SMTP_HOST must be set")
return nil, ErrMissingConfig return nil, ErrMissingConfig
} }
if smtp.Port == "" { if smtp.Port == "" {
slog.Error("SMTP_PORT must be set") slog.ErrorContext(ctx, "SMTP_PORT must be set")
return nil, ErrMissingConfig return nil, ErrMissingConfig
} }
if smtp.User == "" { if smtp.User == "" {
slog.Error("SMTP_USER must be set") slog.ErrorContext(ctx, "SMTP_USER must be set")
return nil, ErrMissingConfig return nil, ErrMissingConfig
} }
if smtp.Pass == "" { if smtp.Pass == "" {
slog.Error("SMTP_PASS must be set") slog.ErrorContext(ctx, "SMTP_PASS must be set")
return nil, ErrMissingConfig return nil, ErrMissingConfig
} }
if smtp.FromMail == "" { if smtp.FromMail == "" {
slog.Error("SMTP_FROM_MAIL must be set") slog.ErrorContext(ctx, "SMTP_FROM_MAIL must be set")
return nil, ErrMissingConfig return nil, ErrMissingConfig
} }
if smtp.FromName == "" { if smtp.FromName == "" {
slog.Error("SMTP_FROM_NAME must be set") slog.ErrorContext(ctx, "SMTP_FROM_NAME must be set")
return nil, ErrMissingConfig return nil, ErrMissingConfig
} }

View File

@@ -1,6 +1,7 @@
package utils package utils
import ( import (
"context"
"fmt" "fmt"
"log/slog" "log/slog"
"net/http" "net/http"
@@ -8,16 +9,16 @@ import (
"time" "time"
) )
func TriggerToast(w http.ResponseWriter, r *http.Request, class string, message string) { func TriggerToast(ctx context.Context, w http.ResponseWriter, r *http.Request, class string, message string) {
if IsHtmx(r) { if IsHtmx(r) {
w.Header().Set("Hx-Trigger", fmt.Sprintf(`{"toast": "%v|%v"}`, class, strings.ReplaceAll(message, `"`, `\"`))) w.Header().Set("Hx-Trigger", fmt.Sprintf(`{"toast": "%v|%v"}`, class, strings.ReplaceAll(message, `"`, `\"`)))
} else { } else {
slog.Error("Trying to trigger toast in non-HTMX request") slog.ErrorContext(ctx, "Trying to trigger toast in non-HTMX request")
} }
} }
func TriggerToastWithStatus(w http.ResponseWriter, r *http.Request, class string, message string, statusCode int) { func TriggerToastWithStatus(ctx context.Context, w http.ResponseWriter, r *http.Request, class string, message string, statusCode int) {
TriggerToast(w, r, class, message) TriggerToast(ctx, w, r, class, message)
w.WriteHeader(statusCode) w.WriteHeader(statusCode)
} }

10
main.go
View File

@@ -14,26 +14,28 @@ import (
) )
func main() { func main() {
ctx := context.Background()
err := godotenv.Load() err := godotenv.Load()
if err != nil { if err != nil {
slog.Error("Error loading .env file") slog.ErrorContext(ctx, "Error loading .env file")
return return
} }
db, err := otelsqlx.Open("sqlite3", "./data/spend-sparrow.db", db, err := otelsqlx.Open("sqlite3", "./data/spend-sparrow.db",
otelsql.WithAttributes(semconv.DBSystemSqlite)) otelsql.WithAttributes(semconv.DBSystemSqlite))
if err != nil { if err != nil {
slog.Error("Could not open Database data.db", "err", err) slog.ErrorContext(ctx, "Could not open Database data.db", "err", err)
return return
} }
defer func() { defer func() {
if err = db.Close(); err != nil { if err = db.Close(); err != nil {
slog.Error("Database close failed", "err", err) slog.ErrorContext(ctx, "Database close failed", "err", err)
} }
}() }()
if err = internal.Run(context.Background(), db, "", os.Getenv); err != nil { if err = internal.Run(context.Background(), db, "", os.Getenv); err != nil {
slog.Error("Error running server", "err", err) slog.ErrorContext(ctx, "Error running server", "err", err)
return return
} }
} }

View File

@@ -79,8 +79,10 @@ func TestSignUp(t *testing.T) {
expected := types.NewUser(userId, email, false, nil, false, service.GetHashPassword(password, salt), salt, createTime) expected := types.NewUser(userId, email, false, nil, false, service.GetHashPassword(password, salt), salt, createTime)
mockRandom.EXPECT().UUID().Return(userId, nil) ctx := context.Background()
mockRandom.EXPECT().Bytes(16).Return(salt, nil)
mockRandom.EXPECT().UUID(ctx).Return(userId, nil)
mockRandom.EXPECT().Bytes(ctx, 16).Return(salt, nil)
mockClock.EXPECT().Now().Return(createTime) mockClock.EXPECT().Now().Return(createTime)
mockAuthDb.EXPECT().InsertUser(context.Background(), expected).Return(nil) mockAuthDb.EXPECT().InsertUser(context.Background(), expected).Return(nil)
@@ -106,8 +108,9 @@ func TestSignUp(t *testing.T) {
salt := []byte("salt") salt := []byte("salt")
user := types.NewUser(userId, email, false, nil, false, service.GetHashPassword(password, salt), salt, createTime) user := types.NewUser(userId, email, false, nil, false, service.GetHashPassword(password, salt), salt, createTime)
mockRandom.EXPECT().UUID().Return(user.Id, nil) ctx := context.Background()
mockRandom.EXPECT().Bytes(16).Return(salt, nil) mockRandom.EXPECT().UUID(ctx).Return(user.Id, nil)
mockRandom.EXPECT().Bytes(ctx, 16).Return(salt, nil)
mockClock.EXPECT().Now().Return(createTime) mockClock.EXPECT().Now().Return(createTime)
mockAuthDb.EXPECT().InsertUser(context.Background(), user).Return(db.ErrAlreadyExists) mockAuthDb.EXPECT().InsertUser(context.Background(), user).Return(db.ErrAlreadyExists)
@@ -141,9 +144,9 @@ func TestSendVerificationMail(t *testing.T) {
mockClock := mocks.NewMockClock(t) mockClock := mocks.NewMockClock(t)
mockMail := mocks.NewMockMail(t) mockMail := mocks.NewMockMail(t)
ctx := context.Background()
mockAuthDb.EXPECT().GetTokensByUserIdAndType(context.Background(), userId, types.TokenTypeEmailVerify).Return(tokens, nil) mockAuthDb.EXPECT().GetTokensByUserIdAndType(context.Background(), userId, types.TokenTypeEmailVerify).Return(tokens, nil)
mockMail.EXPECT().SendMail(ctx, email, "Welcome to spend-sparrow", mock.MatchedBy(func(message string) bool {
mockMail.EXPECT().SendMail(email, "Welcome to spend-sparrow", mock.MatchedBy(func(message string) bool {
return strings.Contains(message, token.Token) return strings.Contains(message, token.Token)
})).Return() })).Return()