From 6c92206b3c11064bad313e1ba1cdf46b3ba1bf41 Mon Sep 17 00:00:00 2001 From: Tim Wundenberg Date: Tue, 17 Jun 2025 09:42:19 +0200 Subject: [PATCH] fix(observabillity): propagate ctx to every log call and add resource to logging --- internal/db/auth.go | 64 +++++++-------- internal/db/error.go | 9 ++- internal/db/migration.go | 6 +- internal/default.go | 24 +++--- internal/handler/auth.go | 42 +++++----- internal/handler/default.go | 8 +- .../middleware/cross_site_request_forgery.go | 6 +- internal/handler/middleware/gzip.go | 2 +- internal/handler/middleware/logger.go | 2 +- internal/handler/render.go | 2 +- internal/handler/root_and_404.go | 2 +- internal/handler/transaction.go | 2 +- internal/handler/transaction_recurring.go | 2 +- internal/log/default.go | 2 - internal/otel.go | 22 ++++- internal/service/account.go | 32 ++++---- internal/service/auth.go | 28 +++---- internal/service/dashboard.go | 10 +-- internal/service/mail.go | 13 +-- internal/service/random_generator.go | 21 ++--- internal/service/transaction.go | 74 ++++++++--------- internal/service/transaction_recurring.go | 80 +++++++++---------- internal/service/treasure_chest.go | 36 ++++----- internal/types/settings.go | 31 +++---- internal/utils/http.go | 9 ++- main.go | 10 ++- test/auth_test.go | 15 ++-- 27 files changed, 288 insertions(+), 266 deletions(-) diff --git a/internal/db/auth.go b/internal/db/auth.go index d581377..f4eb361 100644 --- a/internal/db/auth.go +++ b/internal/db/auth.go @@ -53,7 +53,7 @@ func (db AuthSqlite) InsertUser(ctx context.Context, user *types.User) error { return ErrAlreadyExists } - slog.Error("SQL error InsertUser", "err", err) + slog.ErrorContext(ctx, "SQL error InsertUser", "err", err) 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) if err != nil { - slog.Error("SQL error UpdateUser", "err", err) + slog.ErrorContext(ctx, "SQL error UpdateUser", "err", err) return types.ErrInternal } @@ -94,7 +94,7 @@ func (db AuthSqlite) GetUserByEmail(ctx context.Context, email string) (*types.U if errors.Is(err, sql.ErrNoRows) { return nil, ErrNotFound } else { - slog.Error("SQL error GetUser", "err", err) + slog.ErrorContext(ctx, "SQL error GetUser", "err", err) 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) { return nil, ErrNotFound } else { - slog.Error("SQL error GetUser", "err", err) + slog.ErrorContext(ctx, "SQL error GetUser", "err", err) 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 { tx, err := db.db.BeginTx(ctx, nil) if err != nil { - slog.Error("Could not start transaction", "err", err) + slog.ErrorContext(ctx, "Could not start transaction", "err", err) return types.ErrInternal } _, err = tx.ExecContext(ctx, "DELETE FROM account WHERE user_id = ?", userId) if err != nil { _ = tx.Rollback() - slog.Error("Could not delete accounts", "err", err) + slog.ErrorContext(ctx, "Could not delete accounts", "err", err) return types.ErrInternal } _, err = tx.ExecContext(ctx, "DELETE FROM token WHERE user_id = ?", userId) if err != nil { _ = tx.Rollback() - slog.Error("Could not delete user tokens", "err", err) + slog.ErrorContext(ctx, "Could not delete user tokens", "err", err) return types.ErrInternal } _, err = tx.ExecContext(ctx, "DELETE FROM session WHERE user_id = ?", userId) if err != nil { _ = tx.Rollback() - slog.Error("Could not delete sessions", "err", err) + slog.ErrorContext(ctx, "Could not delete sessions", "err", err) return types.ErrInternal } _, err = tx.ExecContext(ctx, "DELETE FROM user WHERE user_id = ?", userId) if err != nil { _ = tx.Rollback() - slog.Error("Could not delete user", "err", err) + slog.ErrorContext(ctx, "Could not delete user", "err", err) return types.ErrInternal } _, err = tx.ExecContext(ctx, "DELETE FROM treasure_chest WHERE user_id = ?", userId) if err != nil { _ = tx.Rollback() - slog.Error("Could not delete user", "err", err) + slog.ErrorContext(ctx, "Could not delete user", "err", err) return types.ErrInternal } _, err = tx.ExecContext(ctx, "DELETE FROM \"transaction\" WHERE user_id = ?", userId) if err != nil { _ = tx.Rollback() - slog.Error("Could not delete user", "err", err) + slog.ErrorContext(ctx, "Could not delete user", "err", err) return types.ErrInternal } err = tx.Commit() if err != nil { - slog.Error("Could not commit transaction", "err", err) + slog.ErrorContext(ctx, "Could not commit transaction", "err", err) 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) if err != nil { - slog.Error("Could not insert token", "err", err) + slog.ErrorContext(ctx, "Could not insert token", "err", err) return types.ErrInternal } @@ -218,23 +218,23 @@ func (db AuthSqlite) GetToken(ctx context.Context, token string) (*types.Token, if err != nil { if errors.Is(err, sql.ErrNoRows) { - slog.Info("Token not found", "token", token) + slog.InfoContext(ctx, "Token not found", "token", token) return nil, ErrNotFound } else { - slog.Error("Could not get token", "err", err) + slog.ErrorContext(ctx, "Could not get token", "err", err) return nil, types.ErrInternal } } createdAt, err = time.Parse(time.RFC3339, createdAtStr) 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 } expiresAt, err = time.Parse(time.RFC3339, expiresAtStr) 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 } @@ -249,11 +249,11 @@ func (db AuthSqlite) GetTokensByUserIdAndType(ctx context.Context, userId uuid.U AND type = ?`, userId, tokenType) 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 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) { @@ -264,14 +264,14 @@ func (db AuthSqlite) GetTokensBySessionIdAndType(ctx context.Context, sessionId AND type = ?`, sessionId, tokenType) 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 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 hasRows := false @@ -288,19 +288,19 @@ func getTokensFromQuery(query *sql.Rows, userId uuid.UUID, sessionId string, tok err := query.Scan(&token, &createdAtStr, &expiresAtStr) if err != nil { - slog.Error("Could not scan token", "err", err) + slog.ErrorContext(ctx, "Could not scan token", "err", err) return nil, types.ErrInternal } createdAt, err = time.Parse(time.RFC3339, createdAtStr) 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 } expiresAt, err = time.Parse(time.RFC3339, expiresAtStr) 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 } @@ -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 { _, err := db.db.ExecContext(ctx, "DELETE FROM token WHERE token = ?", token) if err != nil { - slog.Error("Could not delete token", "err", err) + slog.ErrorContext(ctx, "Could not delete token", "err", err) return types.ErrInternal } 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) 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 } @@ -349,7 +349,7 @@ func (db AuthSqlite) GetSession(ctx context.Context, sessionId string) (*types.S WHERE session_id = ?`, sessionId).Scan(&userId, &createdAt, &expiresAt) 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 } @@ -363,7 +363,7 @@ func (db AuthSqlite) GetSessions(ctx context.Context, userId uuid.UUID) ([]*type FROM session WHERE user_id = ?`, userId) if err != nil { - slog.Error("Could not get sessions", "err", err) + slog.ErrorContext(ctx, "Could not get sessions", "err", err) return nil, types.ErrInternal } @@ -374,7 +374,7 @@ func (db AuthSqlite) DeleteSession(ctx context.Context, sessionId string) error if sessionId != "" { _, err := db.db.ExecContext(ctx, "DELETE FROM session WHERE session_id = ?", sessionId) if err != nil { - slog.Error("Could not delete session", "err", err) + slog.ErrorContext(ctx, "Could not delete session", "err", err) return types.ErrInternal } } @@ -387,7 +387,7 @@ func (db AuthSqlite) DeleteOldSessions(ctx context.Context) error { DELETE FROM session WHERE expires_at < datetime('now')`) 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 nil @@ -398,7 +398,7 @@ func (db AuthSqlite) DeleteOldTokens(ctx context.Context) error { DELETE FROM token WHERE expires_at < datetime('now')`) 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 nil diff --git a/internal/db/error.go b/internal/db/error.go index 8078340..f81fe7e 100644 --- a/internal/db/error.go +++ b/internal/db/error.go @@ -1,6 +1,7 @@ package db import ( + "context" "database/sql" "errors" "log/slog" @@ -12,24 +13,24 @@ var ( 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 errors.Is(err, sql.ErrNoRows) { return ErrNotFound } - slog.Error("database sql", "module", module, "err", err) + slog.ErrorContext(ctx, "database sql", "module", module, "err", err) return types.ErrInternal } if r != nil { rows, err := r.RowsAffected() 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 } if rows == 0 { - slog.Info("row not found", "module", module) + slog.InfoContext(ctx, "row not found", "module", module) return ErrNotFound } } diff --git a/internal/db/migration.go b/internal/db/migration.go index 6638ada..76ed2d9 100644 --- a/internal/db/migration.go +++ b/internal/db/migration.go @@ -24,7 +24,7 @@ func (l migrationLogger) Verbose() bool { func RunMigrations(ctx context.Context, db *sqlx.DB, pathPrefix string) error { driver, err := sqlite3.WithInstance(db.DB, &sqlite3.Config{}) 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 } @@ -33,14 +33,14 @@ func RunMigrations(ctx context.Context, db *sqlx.DB, pathPrefix string) error { "", driver) 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 } m.Log = migrationLogger{} 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 } diff --git a/internal/default.go b/internal/default.go index 5206092..285bf84 100644 --- a/internal/default.go +++ b/internal/default.go @@ -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) err = otelShutdown(ctx) if err != nil { - slog.Error("error shutting down OpenTelemetry SDK", "err", err) + slog.ErrorContext(ctx, "error shutting down OpenTelemetry SDK", "err", err) } cancel() }() @@ -47,10 +47,10 @@ func Run(ctx context.Context, database *sqlx.DB, migrationsPrefix string, env fu slog.SetDefault(log.NewLogPropagator()) } - slog.Info("Starting server...") + slog.InfoContext(ctx, "Starting server...") // init server settings - serverSettings, err := types.NewSettingsFromEnv(env) + serverSettings, err := types.NewSettingsFromEnv(ctx, env) if err != nil { return err } @@ -67,26 +67,26 @@ func Run(ctx context.Context, database *sqlx.DB, migrationsPrefix string, env fu Handler: createHandlerWithServices(ctx, database, serverSettings), ReadHeaderTimeout: 2 * time.Second, } - go startServer(httpServer) + go startServer(ctx, httpServer) // graceful shutdown var wg sync.WaitGroup wg.Add(1) - go shutdownServer(httpServer, ctx, &wg) + go shutdownServer(ctx, httpServer, &wg) wg.Wait() return nil } -func startServer(s *http.Server) { - slog.Info("Starting server", "addr", s.Addr) +func startServer(ctx context.Context, s *http.Server) { + slog.InfoContext(ctx, "Starting server", "addr", s.Addr) 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() if s == nil { return @@ -97,9 +97,9 @@ func shutdownServer(s *http.Server, ctx context.Context, wg *sync.WaitGroup) { shutdownCtx, cancel := context.WithTimeout(shutdownCtx, 10*time.Second) defer cancel() 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 { - 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) { - slog.Info("Running daily tasks") + slog.InfoContext(ctx, "Running daily tasks") _ = transactionRecurring.GenerateTransactions(ctx) _ = auth.CleanupSessionsAndTokens(ctx) } diff --git a/internal/handler/auth.go b/internal/handler/auth.go index 73729d9..95558f9 100644 --- a/internal/handler/auth.go +++ b/internal/handler/auth.go @@ -98,9 +98,9 @@ func (handler AuthImpl) handleSignIn() http.HandlerFunc { if err != nil { 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 { - utils.TriggerToastWithStatus(w, r, "error", "An error occurred", http.StatusInternalServerError) + utils.TriggerToastWithStatus(r.Context(), w, r, "error", "An error occurred", http.StatusInternalServerError) } return } @@ -167,7 +167,7 @@ func (handler AuthImpl) handleVerifyResendComp() http.HandlerFunc { _, err := w.Write([]byte("

Verification email sent

")) 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") _, 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) if err != nil { 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) return nil, nil }) @@ -216,19 +216,19 @@ func (handler AuthImpl) handleSignUp() http.HandlerFunc { if err != nil { switch { 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 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 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 } // 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) if err != nil { 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 { - utils.TriggerToastWithStatus(w, r, "error", "Internal Server Error", http.StatusInternalServerError) + utils.TriggerToastWithStatus(r.Context(), w, r, "error", "Internal Server Error", http.StatusInternalServerError) } return } @@ -327,7 +327,7 @@ func (handler AuthImpl) handleChangePasswordComp() http.HandlerFunc { session := middleware.GetSession(r) user := middleware.GetUser(r) if session == nil || user == nil { - utils.TriggerToastWithStatus(w, r, "error", "Unathorized", http.StatusUnauthorized) + utils.TriggerToastWithStatus(r.Context(), w, r, "error", "Unathorized", http.StatusUnauthorized) return } @@ -336,11 +336,11 @@ func (handler AuthImpl) handleChangePasswordComp() http.HandlerFunc { err := handler.service.ChangePassword(r.Context(), user, session.Id, currPass, newPass) if err != nil { - utils.TriggerToastWithStatus(w, r, "error", err.Error(), http.StatusBadRequest) + utils.TriggerToastWithStatus(r.Context(), w, r, "error", err.Error(), http.StatusBadRequest) 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") 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 } @@ -375,9 +375,9 @@ func (handler AuthImpl) handleForgotPasswordComp() http.HandlerFunc { }) 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 { - 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")) if err != nil { - slog.Error("Could not get current URL", "err", err) - utils.TriggerToastWithStatus(w, r, "error", "Internal Server Error", http.StatusInternalServerError) + slog.ErrorContext(r.Context(), "Could not get current URL", "err", err) + utils.TriggerToastWithStatus(r.Context(), w, r, "error", "Internal Server Error", http.StatusInternalServerError) return } @@ -398,9 +398,9 @@ func (handler AuthImpl) handleForgotPasswordResponseComp() http.HandlerFunc { err = handler.service.ForgotPassword(r.Context(), token, newPass) if err != nil { - utils.TriggerToastWithStatus(w, r, "error", err.Error(), http.StatusBadRequest) + utils.TriggerToastWithStatus(r.Context(), w, r, "error", err.Error(), http.StatusBadRequest) } else { - utils.TriggerToastWithStatus(w, r, "success", "Password changed", http.StatusOK) + utils.TriggerToastWithStatus(r.Context(), w, r, "success", "Password changed", http.StatusOK) } } } diff --git a/internal/handler/default.go b/internal/handler/default.go index 4b8bb39..c1b5f8b 100644 --- a/internal/handler/default.go +++ b/internal/handler/default.go @@ -15,17 +15,17 @@ import ( func handleError(w http.ResponseWriter, r *http.Request, err error) { switch { 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 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 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 } - 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 { diff --git a/internal/handler/middleware/cross_site_request_forgery.go b/internal/handler/middleware/cross_site_request_forgery.go index be3c77e..b94c036 100644 --- a/internal/handler/middleware/cross_site_request_forgery.go +++ b/internal/handler/middleware/cross_site_request_forgery.go @@ -42,9 +42,9 @@ func CrossSiteRequestForgery(auth service.Auth) func(http.Handler) http.Handler csrfToken := r.Header.Get("Csrf-Token") 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" { - utils.TriggerToastWithStatus(w, r, "error", "CSRF-Token not correct", http.StatusBadRequest) + utils.TriggerToastWithStatus(ctx, w, r, "error", "CSRF-Token not correct", http.StatusBadRequest) } else { 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) if err != nil { 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 { http.Error(w, "Could not generate CSRF Token", http.StatusBadRequest) } diff --git a/internal/handler/middleware/gzip.go b/internal/handler/middleware/gzip.go index 8ed9ed6..3cddf80 100644 --- a/internal/handler/middleware/gzip.go +++ b/internal/handler/middleware/gzip.go @@ -33,7 +33,7 @@ func Gzip(next http.Handler) http.Handler { err := gz.Close() 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) } }) } diff --git a/internal/handler/middleware/logger.go b/internal/handler/middleware/logger.go index 7a1ea16..d2b3930 100644 --- a/internal/handler/middleware/logger.go +++ b/internal/handler/middleware/logger.go @@ -26,7 +26,7 @@ func Log(next http.Handler) http.Handler { } next.ServeHTTP(wrapped, r) - slog.Info("request", + slog.InfoContext(r.Context(), "request", "remoteAddr", r.RemoteAddr, "status", wrapped.StatusCode, "method", r.Method, diff --git a/internal/handler/render.go b/internal/handler/render.go index 85a05e8..4e78403 100644 --- a/internal/handler/render.go +++ b/internal/handler/render.go @@ -22,7 +22,7 @@ func (render *Render) RenderWithStatus(r *http.Request, w http.ResponseWriter, c w.WriteHeader(status) err := comp.Render(r.Context(), w) 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) } } diff --git a/internal/handler/root_and_404.go b/internal/handler/root_and_404.go index 3f314e1..d4be02f 100644 --- a/internal/handler/root_and_404.go +++ b/internal/handler/root_and_404.go @@ -57,7 +57,7 @@ func (handler IndexImpl) handleRootAnd404() http.HandlerFunc { var err error comp, err = handler.dashboard(user, htmx, r) if err != nil { - slog.Error("Failed to get dashboard summary", "err", err) + slog.ErrorContext(r.Context(), "Failed to get dashboard summary", "err", err) } } else { comp = template.Index() diff --git a/internal/handler/transaction.go b/internal/handler/transaction.go index 4caf686..ff130da 100644 --- a/internal/handler/transaction.go +++ b/internal/handler/transaction.go @@ -254,7 +254,7 @@ func (h TransactionImpl) handleRecalculate() http.HandlerFunc { 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) } } diff --git a/internal/handler/transaction_recurring.go b/internal/handler/transaction_recurring.go index c084cef..5d1582f 100644 --- a/internal/handler/transaction_recurring.go +++ b/internal/handler/transaction_recurring.go @@ -115,7 +115,7 @@ func (h TransactionRecurringImpl) renderItems(w http.ResponseWriter, r *http.Req var transactionsRecurring []*types.TransactionRecurring var err error 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 != "" { transactionsRecurring, err = h.s.GetAllByAccount(r.Context(), user, accountId) diff --git a/internal/log/default.go b/internal/log/default.go index a884152..e673ca9 100644 --- a/internal/log/default.go +++ b/internal/log/default.go @@ -27,8 +27,6 @@ func (l *logHandler) Enabled(ctx context.Context, level slog.Level) bool { // Handle implements slog.Handler. 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 { return err } diff --git a/internal/otel.go b/internal/otel.go index 2467c51..67c45bf 100644 --- a/internal/otel.go +++ b/internal/otel.go @@ -3,6 +3,7 @@ package internal import ( "context" "errors" + "log/slog" "time" "go.opentelemetry.io/otel" @@ -13,7 +14,9 @@ import ( "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/log" "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.26.0" ) var ( @@ -47,6 +50,14 @@ func setupOTelSDK(ctx context.Context) (func(context.Context) error, error) { prop := newPropagator() 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. tracerProvider, err := newTracerProvider(ctx) if err != nil { @@ -66,7 +77,7 @@ func setupOTelSDK(ctx context.Context) (func(context.Context) error, error) { otel.SetMeterProvider(meterProvider) // Set up logger provider. - loggerProvider, err := newLoggerProvider(ctx) + loggerProvider, err := newLoggerProvider(ctx, resources) if err != nil { handleErr(ctx, err) return nil, err @@ -85,7 +96,8 @@ func newPropagator() propagation.TextMapPropagator { } func newTracerProvider(ctx context.Context) (*trace.TracerProvider, error) { - exp, err := otlptracegrpc.New(ctx, + exp, err := otlptracegrpc.New( + ctx, otlptracegrpc.WithEndpoint(otelEndpoint), otlptracegrpc.WithInsecure(), ) @@ -97,7 +109,8 @@ func newTracerProvider(ctx context.Context) (*trace.TracerProvider, error) { } func newMeterProvider(ctx context.Context) (*metric.MeterProvider, error) { - exp, err := otlpmetricgrpc.New(ctx, + exp, err := otlpmetricgrpc.New( + ctx, otlpmetricgrpc.WithInsecure(), otlpmetricgrpc.WithEndpoint(otelEndpoint)) if err != nil { @@ -110,7 +123,7 @@ func newMeterProvider(ctx context.Context) (*metric.MeterProvider, error) { 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( ctx, otlploggrpc.WithInsecure(), @@ -121,6 +134,7 @@ func newLoggerProvider(ctx context.Context) (*log.LoggerProvider, error) { loggerProvider := log.NewLoggerProvider( log.WithProcessor(log.NewBatchProcessor(logExporter)), + log.WithResource(resource), ) return loggerProvider, nil } diff --git a/internal/service/account.go b/internal/service/account.go index 4e1ff3a..e89ca96 100644 --- a/internal/service/account.go +++ b/internal/service/account.go @@ -39,7 +39,7 @@ func (s AccountImpl) Add(ctx context.Context, user *types.User, name string) (*t return nil, ErrUnauthorized } - newId, err := s.random.UUID() + newId, err := s.random.UUID(ctx) if err != nil { 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, ` 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) - err = db.TransformAndLogDbError("account Insert", r, err) + err = db.TransformAndLogDbError(ctx, "account Insert", r, err) if err != nil { return nil, err } @@ -86,12 +86,12 @@ func (s AccountImpl) UpdateName(ctx context.Context, user *types.User, id string } uuid, err := uuid.Parse(id) 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) } 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 { return nil, err } @@ -101,7 +101,7 @@ func (s AccountImpl) UpdateName(ctx context.Context, user *types.User, id string var account types.Account 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 errors.Is(err, db.ErrNotFound) { 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 WHERE id = :id AND user_id = :user_id`, account) - err = db.TransformAndLogDbError("account Update", r, err) + err = db.TransformAndLogDbError(ctx, "account Update", r, err) if err != nil { return nil, err } err = tx.Commit() - err = db.TransformAndLogDbError("account Update", nil, err) + err = db.TransformAndLogDbError(ctx, "account Update", nil, err) if err != nil { 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) 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) } var account types.Account err = s.db.GetContext(ctx, &account, ` 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 { - slog.Error("account get", "err", err) + slog.ErrorContext(ctx, "account get", "err", 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) err := s.db.SelectContext(ctx, &accounts, ` 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 { 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) 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) } 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 { return err } @@ -195,7 +195,7 @@ func (s AccountImpl) Delete(ctx context.Context, user *types.User, id string) er transactionsCount := 0 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 { 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) - err = db.TransformAndLogDbError("account Delete", res, err) + err = db.TransformAndLogDbError(ctx, "account Delete", res, err) if err != nil { return err } err = tx.Commit() - err = db.TransformAndLogDbError("account Delete", nil, err) + err = db.TransformAndLogDbError(ctx, "account Delete", nil, err) if err != nil { return err } diff --git a/internal/service/auth.go b/internal/service/auth.go index df74d7c..06e782a 100644 --- a/internal/service/auth.go +++ b/internal/service/auth.go @@ -138,7 +138,7 @@ func (service AuthImpl) SignInAnonymous(ctx context.Context) (*types.Session, er 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 } @@ -153,12 +153,12 @@ func (service AuthImpl) SignUp(ctx context.Context, email string, password strin return nil, ErrInvalidPassword } - userId, err := service.random.UUID() + userId, err := service.random.UUID(ctx) if err != nil { return nil, types.ErrInternal } - salt, err := service.random.Bytes(16) + salt, err := service.random.Bytes(ctx, 16) if err != nil { return nil, types.ErrInternal } @@ -192,7 +192,7 @@ func (service AuthImpl) SendVerificationMail(ctx context.Context, userId uuid.UU } if token == nil { - newTokenStr, err := service.random.String(32) + newTokenStr, err := service.random.String(ctx, 32) if err != nil { return } @@ -214,11 +214,11 @@ func (service AuthImpl) SendVerificationMail(ctx context.Context, userId uuid.UU var w strings.Builder err = mailTemplate.Register(service.serverSettings.BaseUrl, token.Token).Render(context.Background(), &w) if err != nil { - slog.Error("Could not render welcome email", "err", err) + slog.ErrorContext(ctx, "Could not render welcome email", "err", err) 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 { @@ -278,7 +278,7 @@ func (service AuthImpl) DeleteAccount(ctx context.Context, user *types.User, cur 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 } @@ -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 { - tokenStr, err := service.random.String(32) + tokenStr, err := service.random.String(ctx, 32) if err != nil { return err } @@ -353,10 +353,10 @@ func (service AuthImpl) SendForgotPasswordMail(ctx context.Context, email string var mail strings.Builder err = mailTemplate.ResetPassword(service.serverSettings.BaseUrl, token.Token).Render(context.Background(), &mail) 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 } - service.mail.SendMail(email, "Reset Password", mail.String()) + service.mail.SendMail(ctx, email, "Reset Password", mail.String()) return nil } @@ -383,7 +383,7 @@ func (service AuthImpl) ForgotPassword(ctx context.Context, tokenStr string, new user, err := service.db.GetUser(ctx, token.UserId) 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 } @@ -436,7 +436,7 @@ func (service AuthImpl) GetCsrfToken(ctx context.Context, session *types.Session return tokens[0].Token, nil } - tokenStr, err := service.random.String(32) + tokenStr, err := service.random.String(ctx, 32) if err != nil { return "", types.ErrInternal } @@ -453,7 +453,7 @@ func (service AuthImpl) GetCsrfToken(ctx context.Context, session *types.Session return "", types.ErrInternal } - slog.Info("CSRF-Token created", "token", tokenStr) + slog.InfoContext(ctx, "CSRF-Token created", "token", tokenStr) 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) { - sessionId, err := service.random.String(32) + sessionId, err := service.random.String(ctx, 32) if err != nil { return nil, types.ErrInternal } diff --git a/internal/service/dashboard.go b/internal/service/dashboard.go index 61a9914..1a79df3 100644 --- a/internal/service/dashboard.go +++ b/internal/service/dashboard.go @@ -36,7 +36,7 @@ func (s Dashboard) Summary(ctx context.Context, user *types.User, month time.Tim AND error IS NULL AND date(timestamp, 'start of month') = date($2, 'start of month')`, user.Id, month) - err = db.TransformAndLogDbError("dashboard", nil, err) + err = db.TransformAndLogDbError(ctx, "dashboard", nil, err) if err != nil { 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 date(timestamp, 'start of month') = date($2, 'start of month')`, user.Id, month) - err = db.TransformAndLogDbError("dashboard", nil, err) + err = db.TransformAndLogDbError(ctx, "dashboard", nil, err) if err != nil { 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 date(timestamp, 'start of month') = date($2, 'start of month')`, user.Id, month) - err = db.TransformAndLogDbError("dashboard", nil, err) + err = db.TransformAndLogDbError(ctx, "dashboard", nil, err) if err != nil { return nil, err } @@ -86,7 +86,7 @@ func (s Dashboard) Summary(ctx context.Context, user *types.User, month time.Tim FROM treasure_chest WHERE user_id = $1`, user.Id) - err = db.TransformAndLogDbError("dashboard", nil, err) + err = db.TransformAndLogDbError(ctx, "dashboard", nil, err) if err != nil { return nil, err } @@ -99,7 +99,7 @@ func (s Dashboard) Summary(ctx context.Context, user *types.User, month time.Tim FROM account WHERE user_id = $1`, user.Id) - err = db.TransformAndLogDbError("dashboard", nil, err) + err = db.TransformAndLogDbError(ctx, "dashboard", nil, err) if err != nil { return nil, err } diff --git a/internal/service/mail.go b/internal/service/mail.go index b90cbb0..9a0edd7 100644 --- a/internal/service/mail.go +++ b/internal/service/mail.go @@ -1,6 +1,7 @@ package service import ( + "context" "fmt" "log/slog" "net/smtp" @@ -9,7 +10,7 @@ import ( type Mail interface { // 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 { @@ -20,11 +21,11 @@ func NewMail(server *types.Settings) MailImpl { return MailImpl{server: server} } -func (m MailImpl) SendMail(to string, subject string, message string) { - go m.internalSendMail(to, subject, message) +func (m MailImpl) SendMail(ctx context.Context, to string, subject string, message string) { + 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 { return } @@ -47,9 +48,9 @@ func (m MailImpl) internalSendMail(to string, subject string, message string) { subject, 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)) if err != nil { - slog.Error("Error sending mail", "err", err) + slog.ErrorContext(ctx, "Error sending mail", "err", err) } } diff --git a/internal/service/random_generator.go b/internal/service/random_generator.go index d8193f8..331afdc 100644 --- a/internal/service/random_generator.go +++ b/internal/service/random_generator.go @@ -1,6 +1,7 @@ package service import ( + "context" "crypto/rand" "encoding/base64" "log/slog" @@ -10,9 +11,9 @@ import ( ) type Random interface { - Bytes(size int) ([]byte, error) - String(size int) (string, error) - UUID() (uuid.UUID, error) + Bytes(ctx context.Context, size int) ([]byte, error) + String(ctx context.Context, size int) (string, error) + UUID(ctx context.Context) (uuid.UUID, error) } type RandomImpl struct { @@ -22,31 +23,31 @@ func NewRandom() *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) _, err := rand.Read(b) 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 b, nil } -func (r *RandomImpl) String(size int) (string, error) { - bytes, err := r.Bytes(size) +func (r *RandomImpl) String(ctx context.Context, size int) (string, error) { + bytes, err := r.Bytes(ctx, size) if err != nil { - slog.Error("Error generating random string", "err", err) + slog.ErrorContext(ctx, "Error generating random string", "err", err) return "", types.ErrInternal } 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() 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 } diff --git a/internal/service/transaction.go b/internal/service/transaction.go index cabf5ed..5a0ad94 100644 --- a/internal/service/transaction.go +++ b/internal/service/transaction.go @@ -47,7 +47,7 @@ func (s TransactionImpl) Add(ctx context.Context, tx *sqlx.Tx, user *types.User, if tx == nil { ownsTransaction = true 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 { 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) VALUES (:id, :user_id, :account_id, :treasure_chest_id, :value, :timestamp, :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 { return nil, err } @@ -76,7 +76,7 @@ func (s TransactionImpl) Add(ctx context.Context, tx *sqlx.Tx, user *types.User, UPDATE account SET current_balance = current_balance + ? 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 { return nil, err } @@ -87,7 +87,7 @@ func (s TransactionImpl) Add(ctx context.Context, tx *sqlx.Tx, user *types.User, UPDATE treasure_chest SET current_balance = current_balance + ? 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 { return nil, err } @@ -95,7 +95,7 @@ func (s TransactionImpl) Add(ctx context.Context, tx *sqlx.Tx, user *types.User, if ownsTransaction { err = tx.Commit() - err = db.TransformAndLogDbError("transaction Add", nil, err) + err = db.TransformAndLogDbError(ctx, "transaction Add", nil, err) if err != nil { 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) - err = db.TransformAndLogDbError("transaction Update", nil, err) + err = db.TransformAndLogDbError(ctx, "transaction Update", nil, err) if err != nil { return nil, err } @@ -120,7 +120,7 @@ func (s TransactionImpl) Update(ctx context.Context, user *types.User, input typ transaction := &types.Transaction{} 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 errors.Is(err, db.ErrNotFound) { 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 SET current_balance = current_balance - ? 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 { return nil, err } @@ -143,7 +143,7 @@ func (s TransactionImpl) Update(ctx context.Context, user *types.User, input typ UPDATE treasure_chest SET current_balance = current_balance - ? 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 { return nil, err } @@ -159,7 +159,7 @@ func (s TransactionImpl) Update(ctx context.Context, user *types.User, input typ UPDATE account SET current_balance = current_balance + ? 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 { return nil, err } @@ -169,7 +169,7 @@ func (s TransactionImpl) Update(ctx context.Context, user *types.User, input typ UPDATE treasure_chest SET current_balance = current_balance + ? 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 { return nil, err } @@ -189,13 +189,13 @@ func (s TransactionImpl) Update(ctx context.Context, user *types.User, input typ updated_by = :updated_by WHERE id = :id AND user_id = :user_id`, transaction) - err = db.TransformAndLogDbError("transaction Update", r, err) + err = db.TransformAndLogDbError(ctx, "transaction Update", r, err) if err != nil { return nil, err } err = tx.Commit() - err = db.TransformAndLogDbError("transaction Update", nil, err) + err = db.TransformAndLogDbError(ctx, "transaction Update", nil, err) if err != nil { return nil, err } @@ -209,13 +209,13 @@ func (s TransactionImpl) Get(ctx context.Context, user *types.User, id string) ( } uuid, err := uuid.Parse(id) 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) } var transaction types.Transaction 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 errors.Is(err, db.ErrNotFound) { 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.TreasureChestId, filter.TreasureChestId, filter.Error, filter.Error, filter.Error) - err = db.TransformAndLogDbError("transaction GetAll", nil, err) + err = db.TransformAndLogDbError(ctx, "transaction GetAll", nil, err) if err != nil { return nil, err } @@ -261,12 +261,12 @@ func (s TransactionImpl) Delete(ctx context.Context, user *types.User, id string } uuid, err := uuid.Parse(id) 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) } 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 { return nil } @@ -276,7 +276,7 @@ func (s TransactionImpl) Delete(ctx context.Context, user *types.User, id string var transaction types.Transaction 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 { return err } @@ -287,7 +287,7 @@ func (s TransactionImpl) Delete(ctx context.Context, user *types.User, id string SET current_balance = current_balance - ? WHERE 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) { return err } @@ -299,20 +299,20 @@ func (s TransactionImpl) Delete(ctx context.Context, user *types.User, id string SET current_balance = current_balance - ? WHERE 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) { return err } } 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 { return err } err = tx.Commit() - err = db.TransformAndLogDbError("transaction Delete", nil, err) + err = db.TransformAndLogDbError(ctx, "transaction Delete", nil, err) if err != nil { return err } @@ -326,7 +326,7 @@ func (s TransactionImpl) RecalculateBalances(ctx context.Context, user *types.Us } 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 { return err } @@ -338,7 +338,7 @@ func (s TransactionImpl) RecalculateBalances(ctx context.Context, user *types.Us UPDATE account SET current_balance = 0 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) { return err } @@ -347,7 +347,7 @@ func (s TransactionImpl) RecalculateBalances(ctx context.Context, user *types.Us UPDATE treasure_chest SET current_balance = 0 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) { return err } @@ -356,21 +356,21 @@ func (s TransactionImpl) RecalculateBalances(ctx context.Context, user *types.Us SELECT * FROM "transaction" 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) { return err } defer func() { err := rows.Close() if err != nil { - slog.Error("transaction RecalculateBalances", "err", err) + slog.ErrorContext(ctx, "transaction RecalculateBalances", "err", err) } }() var transaction types.Transaction for rows.Next() { err = rows.StructScan(&transaction) - err = db.TransformAndLogDbError("transaction RecalculateBalances", nil, err) + err = db.TransformAndLogDbError(ctx, "transaction RecalculateBalances", nil, err) if err != nil { return err } @@ -381,7 +381,7 @@ func (s TransactionImpl) RecalculateBalances(ctx context.Context, user *types.Us SET error = ? WHERE user_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 { return err } @@ -395,7 +395,7 @@ func (s TransactionImpl) RecalculateBalances(ctx context.Context, user *types.Us UPDATE account SET current_balance = current_balance + ? 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 { return err } @@ -405,7 +405,7 @@ func (s TransactionImpl) RecalculateBalances(ctx context.Context, user *types.Us UPDATE treasure_chest SET current_balance = current_balance + ? 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 { return err } @@ -413,7 +413,7 @@ func (s TransactionImpl) RecalculateBalances(ctx context.Context, user *types.Us } err = tx.Commit() - err = db.TransformAndLogDbError("transaction RecalculateBalances", nil, err) + err = db.TransformAndLogDbError(ctx, "transaction RecalculateBalances", nil, err) if err != nil { return err } @@ -434,7 +434,7 @@ func (s TransactionImpl) validateAndEnrichTransaction(ctx context.Context, tx *s ) if oldTransaction == nil { - id, err = s.random.UUID() + id, err = s.random.UUID(ctx) if err != nil { return nil, types.ErrInternal } @@ -451,12 +451,12 @@ func (s TransactionImpl) validateAndEnrichTransaction(ctx context.Context, tx *s if input.AccountId != nil { 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 { return nil, err } 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) } } @@ -464,7 +464,7 @@ func (s TransactionImpl) validateAndEnrichTransaction(ctx context.Context, tx *s if input.TreasureChestId != nil { var treasureChest types.TreasureChest 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 errors.Is(err, db.ErrNotFound) { return nil, fmt.Errorf("treasure chest not found: %w", ErrBadRequest) diff --git a/internal/service/transaction_recurring.go b/internal/service/transaction_recurring.go index 111c347..c377840 100644 --- a/internal/service/transaction_recurring.go +++ b/internal/service/transaction_recurring.go @@ -51,7 +51,7 @@ func (s TransactionRecurringImpl) Add(ctx context.Context, } 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 { return nil, err } @@ -70,13 +70,13 @@ func (s TransactionRecurringImpl) Add(ctx context.Context, VALUES (:id, :user_id, :interval_months, :next_execution, :party, :description, :account_id, :treasure_chest_id, :value, :created_at, :created_by)`, transactionRecurring) - err = db.TransformAndLogDbError("transactionRecurring Insert", r, err) + err = db.TransformAndLogDbError(ctx, "transactionRecurring Insert", r, err) if err != nil { return nil, err } err = tx.Commit() - err = db.TransformAndLogDbError("transactionRecurring Add", nil, err) + err = db.TransformAndLogDbError(ctx, "transactionRecurring Add", nil, err) if err != nil { return nil, err } @@ -93,12 +93,12 @@ func (s TransactionRecurringImpl) Update(ctx context.Context, } uuid, err := uuid.Parse(input.Id) 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) } 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 { return nil, err } @@ -108,7 +108,7 @@ func (s TransactionRecurringImpl) Update(ctx context.Context, transactionRecurring := &types.TransactionRecurring{} 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 errors.Is(err, db.ErrNotFound) { 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 WHERE id = :id AND user_id = :user_id`, transactionRecurring) - err = db.TransformAndLogDbError("transactionRecurring Update", r, err) + err = db.TransformAndLogDbError(ctx, "transactionRecurring Update", r, err) if err != nil { return nil, err } err = tx.Commit() - err = db.TransformAndLogDbError("transactionRecurring Update", nil, err) + err = db.TransformAndLogDbError(ctx, "transactionRecurring Update", nil, err) if err != nil { return nil, err } @@ -161,7 +161,7 @@ func (s TransactionRecurringImpl) GetAll(ctx context.Context, user *types.User) WHERE user_id = ? ORDER BY created_at DESC`, user.Id) - err = db.TransformAndLogDbError("transactionRecurring GetAll", nil, err) + err = db.TransformAndLogDbError(ctx, "transactionRecurring GetAll", nil, err) if err != nil { return nil, err } @@ -176,12 +176,12 @@ func (s TransactionRecurringImpl) GetAllByAccount(ctx context.Context, user *typ accountUuid, err := uuid.Parse(accountId) 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) } 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 { return nil, err } @@ -191,7 +191,7 @@ func (s TransactionRecurringImpl) GetAllByAccount(ctx context.Context, user *typ var rowCount int 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 errors.Is(err, db.ErrNotFound) { 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 = ? ORDER BY created_at DESC`, user.Id, accountUuid) - err = db.TransformAndLogDbError("transactionRecurring GetAll", nil, err) + err = db.TransformAndLogDbError(ctx, "transactionRecurring GetAll", nil, err) if err != nil { return nil, err } err = tx.Commit() - err = db.TransformAndLogDbError("transactionRecurring GetAllByAccount", nil, err) + err = db.TransformAndLogDbError(ctx, "transactionRecurring GetAllByAccount", nil, err) if err != nil { return nil, err } @@ -231,12 +231,12 @@ func (s TransactionRecurringImpl) GetAllByTreasureChest(ctx context.Context, treasureChestUuid, err := uuid.Parse(treasureChestId) 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) } 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 { return nil, err } @@ -246,7 +246,7 @@ func (s TransactionRecurringImpl) GetAllByTreasureChest(ctx context.Context, var rowCount int 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 errors.Is(err, db.ErrNotFound) { 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 = ? ORDER BY created_at DESC`, user.Id, treasureChestUuid) - err = db.TransformAndLogDbError("transactionRecurring GetAll", nil, err) + err = db.TransformAndLogDbError(ctx, "transactionRecurring GetAll", nil, err) if err != nil { return nil, err } err = tx.Commit() - err = db.TransformAndLogDbError("transactionRecurring GetAllByTreasureChest", nil, err) + err = db.TransformAndLogDbError(ctx, "transactionRecurring GetAllByTreasureChest", nil, err) if err != nil { return nil, err } @@ -282,12 +282,12 @@ func (s TransactionRecurringImpl) Delete(ctx context.Context, user *types.User, } uuid, err := uuid.Parse(id) 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) } 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 { return nil } @@ -297,19 +297,19 @@ func (s TransactionRecurringImpl) Delete(ctx context.Context, user *types.User, var transactionRecurring types.TransactionRecurring 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 { return err } 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 { return err } err = tx.Commit() - err = db.TransformAndLogDbError("transactionRecurring Delete", nil, err) + err = db.TransformAndLogDbError(ctx, "transactionRecurring Delete", nil, err) if err != nil { return err } @@ -321,7 +321,7 @@ func (s TransactionRecurringImpl) GenerateTransactions(ctx context.Context) erro now := s.clock.Now() 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 { return err } @@ -333,7 +333,7 @@ func (s TransactionRecurringImpl) GenerateTransactions(ctx context.Context) erro err = tx.SelectContext(ctx, &recurringTransactions, ` SELECT * FROM transaction_recurring WHERE next_execution <= ?`, now) - err = db.TransformAndLogDbError("transactionRecurring GenerateTransactions", nil, err) + err = db.TransformAndLogDbError(ctx, "transactionRecurring GenerateTransactions", nil, err) if err != nil { return err } @@ -359,14 +359,14 @@ func (s TransactionRecurringImpl) GenerateTransactions(ctx context.Context) erro nextExecution := transactionRecurring.NextExecution.AddDate(0, int(transactionRecurring.IntervalMonths), 0) r, err := tx.ExecContext(ctx, `UPDATE transaction_recurring SET next_execution = ? WHERE id = ? AND user_id = ?`, nextExecution, transactionRecurring.Id, user.Id) - err = db.TransformAndLogDbError("transactionRecurring GenerateTransactions", r, err) + err = db.TransformAndLogDbError(ctx, "transactionRecurring GenerateTransactions", r, err) if err != nil { return err } } err = tx.Commit() - err = db.TransformAndLogDbError("transactionRecurring GenerateTransactions", nil, err) + err = db.TransformAndLogDbError(ctx, "transactionRecurring GenerateTransactions", nil, err) if err != nil { return err } @@ -395,7 +395,7 @@ func (s TransactionRecurringImpl) validateAndEnrichTransactionRecurring( ) if oldTransactionRecurring == nil { - id, err = s.random.UUID() + id, err = s.random.UUID(ctx) if err != nil { return nil, types.ErrInternal } @@ -415,17 +415,17 @@ func (s TransactionRecurringImpl) validateAndEnrichTransactionRecurring( if input.AccountId != "" { temp, err := uuid.Parse(input.AccountId) 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) } accountUuid = &temp 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 { return nil, err } 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) } @@ -435,13 +435,13 @@ func (s TransactionRecurringImpl) validateAndEnrichTransactionRecurring( if input.TreasureChestId != "" { temp, err := uuid.Parse(input.TreasureChestId) 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) } treasureChestUuid = &temp var treasureChest types.TreasureChest 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 errors.Is(err, db.ErrNotFound) { return nil, fmt.Errorf("treasure chest not found: %w", ErrBadRequest) @@ -455,17 +455,17 @@ func (s TransactionRecurringImpl) validateAndEnrichTransactionRecurring( } 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) } 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) } valueFloat, err := strconv.ParseFloat(input.Value, 64) 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) } value := int64(math.Round(valueFloat * DECIMALS_MULTIPLIER)) @@ -484,18 +484,18 @@ func (s TransactionRecurringImpl) validateAndEnrichTransactionRecurring( } intervalMonths, err = strconv.ParseInt(input.IntervalMonths, 10, 0) 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) } 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) } var nextExecution *time.Time = nil if input.NextExecution != "" { t, err := time.Parse("2006-01-02", input.NextExecution) 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) } diff --git a/internal/service/treasure_chest.go b/internal/service/treasure_chest.go index 521bb98..645dfbc 100644 --- a/internal/service/treasure_chest.go +++ b/internal/service/treasure_chest.go @@ -40,7 +40,7 @@ func (s TreasureChestImpl) Add(ctx context.Context, user *types.User, parentId, return nil, ErrUnauthorized } - newId, err := s.random.UUID() + newId, err := s.random.UUID(ctx) if err != nil { 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, ` 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) - err = db.TransformAndLogDbError("treasureChest Insert", r, err) + err = db.TransformAndLogDbError(ctx, "treasureChest Insert", r, err) if err != nil { return nil, err } @@ -98,12 +98,12 @@ func (s TreasureChestImpl) Update(ctx context.Context, user *types.User, idStr, } id, err := uuid.Parse(idStr) 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) } 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 { return nil, err } @@ -113,7 +113,7 @@ func (s TreasureChestImpl) Update(ctx context.Context, user *types.User, idStr, treasureChest := &types.TreasureChest{} 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 errors.Is(err, db.ErrNotFound) { 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 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 { return nil, err } @@ -156,13 +156,13 @@ func (s TreasureChestImpl) Update(ctx context.Context, user *types.User, idStr, updated_by = :updated_by WHERE id = :id AND user_id = :user_id`, treasureChest) - err = db.TransformAndLogDbError("treasureChest Update", r, err) + err = db.TransformAndLogDbError(ctx, "treasureChest Update", r, err) if err != nil { return nil, err } err = tx.Commit() - err = db.TransformAndLogDbError("treasureChest Update", nil, err) + err = db.TransformAndLogDbError(ctx, "treasureChest Update", nil, err) if err != nil { return nil, err } @@ -176,13 +176,13 @@ func (s TreasureChestImpl) Get(ctx context.Context, user *types.User, id string) } uuid, err := uuid.Parse(id) 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) } var treasureChest types.TreasureChest 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 errors.Is(err, db.ErrNotFound) { 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) 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 { return nil, err } @@ -214,12 +214,12 @@ func (s TreasureChestImpl) Delete(ctx context.Context, user *types.User, idStr s } id, err := uuid.Parse(idStr) 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) } 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 { return nil } @@ -229,7 +229,7 @@ func (s TreasureChestImpl) Delete(ctx context.Context, user *types.User, idStr s childCount := 0 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 { return err } @@ -242,7 +242,7 @@ func (s TreasureChestImpl) Delete(ctx context.Context, user *types.User, idStr s err = tx.GetContext(ctx, &transactionsCount, `SELECT COUNT(*) FROM "transaction" WHERE user_id = ? AND treasure_chest_id = ?`, user.Id, id) - err = db.TransformAndLogDbError("treasureChest Delete", nil, err) + err = db.TransformAndLogDbError(ctx, "treasureChest Delete", nil, err) if err != nil { return err } @@ -254,7 +254,7 @@ func (s TreasureChestImpl) Delete(ctx context.Context, user *types.User, idStr s err = tx.GetContext(ctx, &recurringCount, ` SELECT COUNT(*) FROM transaction_recurring WHERE user_id = ? AND treasure_chest_id = ?`, user.Id, id) - err = db.TransformAndLogDbError("treasureChest Delete", nil, err) + err = db.TransformAndLogDbError(ctx, "treasureChest Delete", nil, err) if err != nil { 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) - err = db.TransformAndLogDbError("treasureChest Delete", r, err) + err = db.TransformAndLogDbError(ctx, "treasureChest Delete", r, err) if err != nil { return err } err = tx.Commit() - err = db.TransformAndLogDbError("treasureChest Delete", nil, err) + err = db.TransformAndLogDbError(ctx, "treasureChest Delete", nil, err) if err != nil { return err } diff --git a/internal/types/settings.go b/internal/types/settings.go index 1d3f47a..d9c9ff3 100644 --- a/internal/types/settings.go +++ b/internal/types/settings.go @@ -1,6 +1,7 @@ package types import ( + "context" "errors" "log/slog" ) @@ -26,13 +27,13 @@ type SmtpSettings struct { FromName string } -func NewSettingsFromEnv(env func(string) string) (*Settings, error) { +func NewSettingsFromEnv(ctx context.Context, env func(string) string) (*Settings, error) { var ( smtp *SmtpSettings err error ) if env("SMTP_ENABLED") == "true" { - smtp, err = getSmtpSettings(env) + smtp, err = getSmtpSettings(ctx, env) if err != nil { return nil, err } @@ -46,26 +47,26 @@ func NewSettingsFromEnv(env func(string) string) (*Settings, error) { } if settings.BaseUrl == "" { - slog.Error("BASE_URL must be set") + slog.ErrorContext(ctx, "BASE_URL must be set") return nil, ErrMissingConfig } if settings.Port == "" { - slog.Error("PORT must be set") + slog.ErrorContext(ctx, "PORT must be set") return nil, ErrMissingConfig } if settings.Environment == "" { - slog.Error("ENVIRONMENT must be set") + slog.ErrorContext(ctx, "ENVIRONMENT must be set") return nil, ErrMissingConfig } - slog.Info("settings read", "BASE_URL", settings.BaseUrl) - slog.Info("settings read", "ENVIRONMENT", settings.Environment) - slog.Info("settings read", "ENVIRONMENT", settings.Environment) + slog.InfoContext(ctx, "settings read", "BASE_URL", settings.BaseUrl) + slog.InfoContext(ctx, "settings read", "ENVIRONMENT", settings.Environment) + slog.InfoContext(ctx, "settings read", "ENVIRONMENT", settings.Environment) return settings, nil } -func getSmtpSettings(env func(string) string) (*SmtpSettings, error) { +func getSmtpSettings(ctx context.Context, env func(string) string) (*SmtpSettings, error) { smtp := SmtpSettings{ Host: env("SMTP_HOST"), Port: env("SMTP_PORT"), @@ -76,27 +77,27 @@ func getSmtpSettings(env func(string) string) (*SmtpSettings, error) { } if smtp.Host == "" { - slog.Error("SMTP_HOST must be set") + slog.ErrorContext(ctx, "SMTP_HOST must be set") return nil, ErrMissingConfig } if smtp.Port == "" { - slog.Error("SMTP_PORT must be set") + slog.ErrorContext(ctx, "SMTP_PORT must be set") return nil, ErrMissingConfig } if smtp.User == "" { - slog.Error("SMTP_USER must be set") + slog.ErrorContext(ctx, "SMTP_USER must be set") return nil, ErrMissingConfig } if smtp.Pass == "" { - slog.Error("SMTP_PASS must be set") + slog.ErrorContext(ctx, "SMTP_PASS must be set") return nil, ErrMissingConfig } if smtp.FromMail == "" { - slog.Error("SMTP_FROM_MAIL must be set") + slog.ErrorContext(ctx, "SMTP_FROM_MAIL must be set") return nil, ErrMissingConfig } if smtp.FromName == "" { - slog.Error("SMTP_FROM_NAME must be set") + slog.ErrorContext(ctx, "SMTP_FROM_NAME must be set") return nil, ErrMissingConfig } diff --git a/internal/utils/http.go b/internal/utils/http.go index 49633de..d198857 100644 --- a/internal/utils/http.go +++ b/internal/utils/http.go @@ -1,6 +1,7 @@ package utils import ( + "context" "fmt" "log/slog" "net/http" @@ -8,16 +9,16 @@ import ( "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) { w.Header().Set("Hx-Trigger", fmt.Sprintf(`{"toast": "%v|%v"}`, class, strings.ReplaceAll(message, `"`, `\"`))) } 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) { - TriggerToast(w, r, class, message) +func TriggerToastWithStatus(ctx context.Context, w http.ResponseWriter, r *http.Request, class string, message string, statusCode int) { + TriggerToast(ctx, w, r, class, message) w.WriteHeader(statusCode) } diff --git a/main.go b/main.go index fc36829..f489072 100644 --- a/main.go +++ b/main.go @@ -14,26 +14,28 @@ import ( ) func main() { + ctx := context.Background() + err := godotenv.Load() if err != nil { - slog.Error("Error loading .env file") + slog.ErrorContext(ctx, "Error loading .env file") return } db, err := otelsqlx.Open("sqlite3", "./data/spend-sparrow.db", otelsql.WithAttributes(semconv.DBSystemSqlite)) 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 } defer func() { 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 { - slog.Error("Error running server", "err", err) + slog.ErrorContext(ctx, "Error running server", "err", err) return } } diff --git a/test/auth_test.go b/test/auth_test.go index b4f8807..f8a1ea5 100644 --- a/test/auth_test.go +++ b/test/auth_test.go @@ -79,8 +79,10 @@ func TestSignUp(t *testing.T) { expected := types.NewUser(userId, email, false, nil, false, service.GetHashPassword(password, salt), salt, createTime) - mockRandom.EXPECT().UUID().Return(userId, nil) - mockRandom.EXPECT().Bytes(16).Return(salt, nil) + ctx := context.Background() + + mockRandom.EXPECT().UUID(ctx).Return(userId, nil) + mockRandom.EXPECT().Bytes(ctx, 16).Return(salt, nil) mockClock.EXPECT().Now().Return(createTime) mockAuthDb.EXPECT().InsertUser(context.Background(), expected).Return(nil) @@ -106,8 +108,9 @@ func TestSignUp(t *testing.T) { salt := []byte("salt") user := types.NewUser(userId, email, false, nil, false, service.GetHashPassword(password, salt), salt, createTime) - mockRandom.EXPECT().UUID().Return(user.Id, nil) - mockRandom.EXPECT().Bytes(16).Return(salt, nil) + ctx := context.Background() + mockRandom.EXPECT().UUID(ctx).Return(user.Id, nil) + mockRandom.EXPECT().Bytes(ctx, 16).Return(salt, nil) mockClock.EXPECT().Now().Return(createTime) mockAuthDb.EXPECT().InsertUser(context.Background(), user).Return(db.ErrAlreadyExists) @@ -141,9 +144,9 @@ func TestSendVerificationMail(t *testing.T) { mockClock := mocks.NewMockClock(t) mockMail := mocks.NewMockMail(t) + ctx := context.Background() mockAuthDb.EXPECT().GetTokensByUserIdAndType(context.Background(), userId, types.TokenTypeEmailVerify).Return(tokens, nil) - - mockMail.EXPECT().SendMail(email, "Welcome to spend-sparrow", mock.MatchedBy(func(message string) bool { + mockMail.EXPECT().SendMail(ctx, email, "Welcome to spend-sparrow", mock.MatchedBy(func(message string) bool { return strings.Contains(message, token.Token) })).Return()