From 83f82a26299b2de13961aa92b37b2a60cf2b7099 Mon Sep 17 00:00:00 2001 From: MichaelBoegner Date: Thu, 18 Sep 2025 11:20:31 +0700 Subject: [PATCH 01/15] added logger to handlers --- handlers/handlers.go | 236 +++++++++++++++++++------------------- handlers/model.go | 6 +- internal/server/server.go | 3 +- 3 files changed, 124 insertions(+), 121 deletions(-) diff --git a/handlers/handlers.go b/handlers/handlers.go index 728dfd5..e337121 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -55,7 +55,7 @@ func (h *Handler) RequestVerificationHandler(w http.ResponseWriter, r *http.Requ verificationJWT, err := user.VerificationToken(req.Email, req.Username, req.Password) if err != nil { - log.Printf("GenerateEmailVerificationToken failed: %v", err) + h.Logger.Error("GenerateEmailVerificationToken failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to create token") return } @@ -63,7 +63,7 @@ func (h *Handler) RequestVerificationHandler(w http.ResponseWriter, r *http.Requ verifyURL := os.Getenv("FRONTEND_URL") + "verify-email?token=" + verificationJWT go func() { if err := h.Mailer.SendVerificationEmail(req.Email, verifyURL); err != nil { - log.Printf("SendVerificationEmail failed: %v", err) + h.Logger.Error("SendVerificationEmail failed: %v", err) } }() @@ -84,7 +84,7 @@ func (h *Handler) CheckEmailHandler(w http.ResponseWriter, r *http.Request) { Email string `json:"email"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - log.Printf("Decoding check-email body failed: %v", err) + h.Logger.Error("Decoding check-email body failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid request body") return } @@ -99,7 +99,7 @@ func (h *Handler) CheckEmailHandler(w http.ResponseWriter, r *http.Request) { RespondWithJSON(w, http.StatusOK, map[string]bool{"exists": false}) return } - log.Printf("CheckEmailHandler internal error: %v", err) + h.Logger.Error("CheckEmailHandler internal error: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } @@ -118,21 +118,21 @@ func (h *Handler) CreateUsersHandler(w http.ResponseWriter, r *http.Request) { } err := json.NewDecoder(r.Body).Decode(&req) if err != nil { - log.Printf("Decoding params failed: %v", err) + h.Logger.Error("Decoding params failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal Server Error") return } userCreated, err := user.CreateUser(h.UserRepo, req.Token) if err != nil { - log.Printf("CreateUser error: %v", err) + h.Logger.Error("CreateUser error: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } err = h.Mailer.SendWelcome(userCreated.Email) if err != nil { - log.Printf("h.Mailer.SendWelcome failed: %v", err) + h.Logger.Error("h.Mailer.SendWelcome failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } @@ -159,20 +159,20 @@ func (h *Handler) GetUsersHandler(w http.ResponseWriter, r *http.Request) { userIDParam, err := GetPathID(r, "/api/users/") if err != nil { - log.Printf("GetPathID error: %v\n", err) + h.Logger.Error("GetPathID error: %v\n", err) RespondWithError(w, http.StatusBadRequest, "UserID required") return } if userID != userIDParam { - log.Printf("UserID mismatch: %v vs. %v", userID, userIDParam) + h.Logger.Error("UserID mismatch: %v vs. %v", userID, userIDParam) RespondWithError(w, http.StatusUnauthorized, "Invalid ID") return } userReturned, err := user.GetUser(h.UserRepo, userID) if err != nil { - log.Printf("GetUsers error: %v", err) + h.Logger.Error("GetUsers error: %v", err) return } @@ -199,48 +199,48 @@ func (h *Handler) DeleteUserHandler(w http.ResponseWriter, r *http.Request) { userIDParam, err := GetPathID(r, "/api/users/delete/") if err != nil { - log.Printf("GetPathID error: %v\n", err) + h.Logger.Error("GetPathID error: %v\n", err) RespondWithError(w, http.StatusBadRequest, "UserID required") return } if userID != userIDParam { - log.Printf("UserID mismatch: %v vs. %v", userID, userIDParam) + h.Logger.Error("UserID mismatch: %v vs. %v", userID, userIDParam) RespondWithError(w, http.StatusUnauthorized, "Invalid ID") return } userReturned, err := user.GetUser(h.UserRepo, userID) if err != nil { - log.Printf("GetUser error: %v", err) + h.Logger.Error("GetUser error: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to find user") return } err = h.Billing.CancelSubscription(h.UserRepo, userReturned.Email) if err != nil { - log.Printf("h.Billing.CancelSubscription failed: %v", err) + h.Logger.Error("h.Billing.CancelSubscription failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } err = user.MarkUserDeleted(h.UserRepo, userID) if err != nil { - log.Printf("DeleteUser failed: %v", err) + h.Logger.Error("DeleteUser failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to delete user") return } err = token.DeleteRefreshToken(h.TokenRepo, userID) if err != nil { - log.Printf("DeleteRefreshTokensForUser failed: %v", err) + h.Logger.Error("DeleteRefreshTokensForUser failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } err = h.Mailer.SendDeletionConfirmation(userReturned.Email) if err != nil { - log.Printf("h.Mailer.SendDeletionConfirmation failed: %v", err) + h.Logger.Error("h.Mailer.SendDeletionConfirmation failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } @@ -257,13 +257,13 @@ func (h *Handler) LoginHandler(w http.ResponseWriter, r *http.Request) { params := &middleware.AcceptedVals{} err := json.NewDecoder(r.Body).Decode(params) if err != nil { - log.Printf("Decoding params failed: %v", err) + h.Logger.Error("Decoding params failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal Server Error") return } if params.Email == "" || params.Password == "" { - log.Printf("Invalid username or password.") + h.Logger.Error("Invalid username or password.") RespondWithError(w, http.StatusBadRequest, "Invalid username or password.") return @@ -271,7 +271,7 @@ func (h *Handler) LoginHandler(w http.ResponseWriter, r *http.Request) { jwToken, username, userID, err := user.LoginUser(h.UserRepo, params.Email, params.Password) if err != nil { - log.Printf("LoginUser error: %v", err) + h.Logger.Error("LoginUser error: %v", err) if errors.Is(err, user.ErrAccountDeleted) { RespondWithError(w, http.StatusUnauthorized, user.ErrAccountDeleted.Error()) return @@ -282,7 +282,7 @@ func (h *Handler) LoginHandler(w http.ResponseWriter, r *http.Request) { refreshToken, err := token.CreateRefreshToken(h.TokenRepo, userID) if err != nil { - log.Printf("RefreshToken error: %v", err) + h.Logger.Error("RefreshToken error: %v", err) RespondWithError(w, http.StatusUnauthorized, "") return } @@ -344,14 +344,14 @@ func (h *Handler) GithubLoginHandler(w http.ResponseWriter, r *http.Request) { client = &http.Client{} req, err = http.NewRequest("GET", "https://api.github.com/user", nil) if err != nil { - log.Printf("http.NewRequest failed: %v", err) + h.Logger.Error("http.NewRequest failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } req.Header.Set("Authorization", "Bearer "+tokenResp.AccessToken) githubResp, err := client.Do(req) if err != nil { - log.Printf("GET api.github.com/user failed: %v", err) + h.Logger.Error("GET api.github.com/user failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } @@ -366,14 +366,14 @@ func (h *Handler) GithubLoginHandler(w http.ResponseWriter, r *http.Request) { if githubUser.Email == "" { req, err := http.NewRequest("GET", "https://api.github.com/user/emails", nil) if err != nil { - log.Printf("http.NewRequest failed: %v", err) + h.Logger.Error("http.NewRequest failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } req.Header.Set("Authorization", "Bearer "+tokenResp.AccessToken) emailResp, err := client.Do(req) if err != nil { - log.Printf("GET api.github.com/user/emails failed: %v", err) + h.Logger.Error("GET api.github.com/user/emails failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } @@ -395,7 +395,7 @@ func (h *Handler) GithubLoginHandler(w http.ResponseWriter, r *http.Request) { } if githubUser.Email == "" { - log.Printf("GitHub login failed: no verified email found for user %s", githubUser.Login) + h.Logger.Error("GitHub login failed: no verified email found for user %s", githubUser.Login) RespondWithError(w, http.StatusUnauthorized, "We couldn’t retrieve a valid email address from GitHub. Please check your GitHub email settings and try again.") return } @@ -408,13 +408,13 @@ func (h *Handler) GithubLoginHandler(w http.ResponseWriter, r *http.Request) { jwt, err := token.CreateJWT(strconv.Itoa(user.ID), 0) if err != nil { - log.Printf("token.CreateJWT failed: %v", err) + h.Logger.Error("token.CreateJWT failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } refreshToken, err := token.CreateRefreshToken(h.TokenRepo, user.ID) if err != nil { - log.Printf("token.CreateRefreshToken failed: %v", err) + h.Logger.Error("token.CreateRefreshToken failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } @@ -443,48 +443,48 @@ func (h *Handler) RefreshTokensHandler(w http.ResponseWriter, r *http.Request) { err := json.NewDecoder(r.Body).Decode(params) if err != nil { - log.Printf("Decoding params failed: %v", err) + h.Logger.Error("Decoding params failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal Server Error") return } if params.UserID == 0 { - log.Printf("Invalid userID") + h.Logger.Error("Invalid userID") RespondWithError(w, http.StatusBadRequest, "Invalid username or password") return } storedToken, err := token.GetStoredRefreshToken(h.TokenRepo, params.UserID) if err != nil { - log.Printf("GetStoredRefreshToken error: %v", err) + h.Logger.Error("GetStoredRefreshToken error: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid user_id") return } ok := token.VerifyRefreshToken(storedToken, providedToken) if !ok { - log.Printf("VerifyRefreshToken error") + h.Logger.Error("VerifyRefreshToken error") RespondWithError(w, http.StatusUnauthorized, "Refresh token is invalid") return } refreshToken, err := token.CreateRefreshToken(h.TokenRepo, params.UserID) if err != nil { - log.Printf("CreateRefreshToken error: %v", err) + h.Logger.Error("CreateRefreshToken error: %v", err) RespondWithError(w, http.StatusUnauthorized, "") return } user, err := h.UserRepo.GetUser(params.UserID) if err != nil || user.AccountStatus == "deleted" { - log.Printf("Refresh attempt for deleted account ID %d", params.UserID) + h.Logger.Error("Refresh attempt for deleted account ID %d", params.UserID) RespondWithError(w, http.StatusUnauthorized, "Account deactivated") return } jwToken, err := token.CreateJWT(strconv.Itoa(params.UserID), 0) if err != nil { - log.Printf("JWT creation failed: %v", err) + h.Logger.Error("JWT creation failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "") return } @@ -512,14 +512,14 @@ func (h *Handler) InterviewsHandler(w http.ResponseWriter, r *http.Request) { params := &middleware.AcceptedVals{} err := json.NewDecoder(r.Body).Decode(params) if err != nil { - log.Printf("Decoding params failed: %v", err) + h.Logger.Error("Decoding params failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal Server Error") return } userReturned, err := user.GetUser(h.UserRepo, userID) if err != nil { - log.Printf("GetUser error: %v", err) + h.Logger.Error("GetUser error: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to find user") return } @@ -537,7 +537,7 @@ func (h *Handler) InterviewsHandler(w http.ResponseWriter, r *http.Request) { if err != nil { var openaiErr *chatgpt.OpenAIError if errors.As(err, &openaiErr) { - log.Printf("OpenAI error: %v", openaiErr) + h.Logger.Error("OpenAI error: %v", openaiErr) RespondWithError(w, openaiErr.StatusCode, openaiErr.Message) return } @@ -545,21 +545,21 @@ func (h *Handler) InterviewsHandler(w http.ResponseWriter, r *http.Request) { RespondWithError(w, http.StatusPaymentRequired, "You do not have enough credits to start a new interview or your subscription has expired.") return } - log.Printf("Interview failed to start: %v", err) + h.Logger.Error("Interview failed to start: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to start interview.") return } conversationID, err := conversation.CreateEmptyConversation(h.ConversationRepo, interviewStarted.Id, interviewStarted.Subtopic) if err != nil { - log.Printf("conversation.CreateEmptyConversation failed: %v", err) + h.Logger.Error("conversation.CreateEmptyConversation failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } err = interview.LinkConversation(h.InterviewRepo, interviewStarted.Id, conversationID) if err != nil { - log.Printf("interview.LinkConversation failed: %v", err) + h.Logger.Error("interview.LinkConversation failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } @@ -587,19 +587,19 @@ func (h *Handler) GetInterviewHandler(w http.ResponseWriter, r *http.Request) { interviewID, err := GetPathID(r, "/api/interviews/") if err != nil { - log.Printf("GetPathID failed: %v", err) + h.Logger.Error("GetPathID failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid interview ID") return } interviewReturned, err := interview.GetInterview(h.InterviewRepo, interviewID) if err != nil { - log.Printf("GetInterview failed: %v", err) + h.Logger.Error("GetInterview failed: %v", err) RespondWithError(w, http.StatusNotFound, "Interview not found") return } if interviewReturned.UserId != userID { - log.Printf("User ID mismatch on interview fetch") + h.Logger.Error("User ID mismatch on interview fetch") RespondWithError(w, http.StatusUnauthorized, "Unauthorized") return } @@ -639,21 +639,21 @@ func (h *Handler) UpdateInterviewStatusHandler(w http.ResponseWriter, r *http.Re interviewReturned, err := interview.GetInterview(h.InterviewRepo, interviewID) if err != nil { - log.Printf("GetInterview error: %v\n", err) + h.Logger.Error("GetInterview error: %v\n", err) RespondWithError(w, http.StatusBadRequest, "Invalid ID") return } err = ValidateInterviewStatusTransition(interviewReturned.Status, payload.Status) if err != nil { - log.Printf("ValidateInterviewStatusTransition failed: %v", err) + h.Logger.Error("ValidateInterviewStatusTransition failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid status transition") return } err = h.InterviewRepo.UpdateStatus(interviewID, userID, payload.Status) if err != nil { - log.Printf("UpdateInterviewStatus failed: %v", err) + h.Logger.Error("UpdateInterviewStatus failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Could not update status") return } @@ -676,7 +676,7 @@ func (h *Handler) CreateConversationsHandler(w http.ResponseWriter, r *http.Requ params := &middleware.AcceptedVals{} err := json.NewDecoder(r.Body).Decode(params) if err != nil { - log.Printf("Decoding params failed: %v", err) + h.Logger.Error("Decoding params failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal Server Error") return } @@ -684,19 +684,19 @@ func (h *Handler) CreateConversationsHandler(w http.ResponseWriter, r *http.Requ interviewID, err := GetPathID(r, "/api/conversations/create/") if err != nil { - log.Printf("PathID error: %v\n", err) + h.Logger.Error("PathID error: %v\n", err) RespondWithError(w, http.StatusBadRequest, "Missing ID") return } interviewReturned, err := interview.GetInterview(h.InterviewRepo, interviewID) if err != nil { - log.Printf("GetInterview error: %v\n", err) + h.Logger.Error("GetInterview error: %v\n", err) RespondWithError(w, http.StatusBadRequest, "Invalid ID") return } if interviewReturned.UserId != userID { - log.Printf("interview.userid != token user_id") + h.Logger.Error("interview.userid != token user_id") RespondWithError(w, http.StatusUnauthorized, "Unauthorized") return } @@ -707,7 +707,7 @@ func (h *Handler) CreateConversationsHandler(w http.ResponseWriter, r *http.Requ conversationReturned, err := conversation.GetConversation(h.ConversationRepo, interviewID) if err != nil { - log.Printf("conversation.GetConversation failed: %v", err) + h.Logger.Error("conversation.GetConversation failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid ID") return } @@ -725,11 +725,11 @@ func (h *Handler) CreateConversationsHandler(w http.ResponseWriter, r *http.Requ if err != nil { var openaiErr *chatgpt.OpenAIError if errors.As(err, &openaiErr) { - log.Printf("OpenAI error: %v", openaiErr) + h.Logger.Error("OpenAI error: %v", openaiErr) RespondWithError(w, openaiErr.StatusCode, openaiErr.Message) return } - log.Printf("CreateConversation error: %v", err) + h.Logger.Error("CreateConversation error: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid interview_id") return } @@ -755,32 +755,32 @@ func (h *Handler) AppendConversationsHandler(w http.ResponseWriter, r *http.Requ params := &middleware.AcceptedVals{} err := json.NewDecoder(r.Body).Decode(params) if err != nil { - log.Printf("Decoding params failed: %v", err) + h.Logger.Error("Decoding params failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal Server Error") return } if params.Message == "" { - log.Printf("messageUserResponse is nil") + h.Logger.Error("messageUserResponse is nil") RespondWithError(w, http.StatusBadRequest, "Missing message") return } interviewID, err := GetPathID(r, "/api/conversations/append/") if err != nil { - log.Printf("PathID error: %v\n", err) + h.Logger.Error("PathID error: %v\n", err) RespondWithError(w, http.StatusBadRequest, "Missing ID") return } interviewReturned, err := interview.GetInterview(h.InterviewRepo, interviewID) if err != nil { - log.Printf("GetInterview error: %v\n", err) + h.Logger.Error("GetInterview error: %v\n", err) RespondWithError(w, http.StatusBadRequest, "Invalid ID") return } if interviewReturned.UserId != userID { - log.Printf("interview.userid != token user_id") + h.Logger.Error("interview.userid != token user_id") RespondWithError(w, http.StatusUnauthorized, "Unauthorized") return } @@ -791,7 +791,7 @@ func (h *Handler) AppendConversationsHandler(w http.ResponseWriter, r *http.Requ conversationReturned, err := conversation.GetConversation(h.ConversationRepo, interviewID) if err != nil { - log.Printf("GetConversation error: %v", err) + h.Logger.Error("GetConversation error: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid ID.") return } @@ -808,11 +808,11 @@ func (h *Handler) AppendConversationsHandler(w http.ResponseWriter, r *http.Requ if err != nil { var openaiErr *chatgpt.OpenAIError if errors.As(err, &openaiErr) { - log.Printf("OpenAI error: %v", openaiErr) + h.Logger.Error("OpenAI error: %v", openaiErr) RespondWithError(w, openaiErr.StatusCode, openaiErr.Message) return } - log.Printf("AppendConversation error: %v", err) + h.Logger.Error("AppendConversation error: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid ID.") return } @@ -837,26 +837,26 @@ func (h *Handler) GetConversationHandler(w http.ResponseWriter, r *http.Request) interviewID, err := GetPathID(r, "/api/conversations/") if err != nil { - log.Printf("PathID error: %v\n", err) + h.Logger.Error("PathID error: %v\n", err) RespondWithError(w, http.StatusBadRequest, "Missing ID") return } interviewReturned, err := interview.GetInterview(h.InterviewRepo, interviewID) if err != nil { - log.Printf("GetInterview error: %v\n", err) + h.Logger.Error("GetInterview error: %v\n", err) RespondWithError(w, http.StatusBadRequest, "Invalid ID") return } if interviewReturned.UserId != userID { - log.Printf("interview.userid != token user_id") + h.Logger.Error("interview.userid != token user_id") RespondWithError(w, http.StatusUnauthorized, "Unauthorized") return } conversationReturned, err := conversation.GetConversation(h.ConversationRepo, interviewID) if err != nil { - log.Printf("GetConversation error: %v", err) + h.Logger.Error("GetConversation error: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid ID.") return } @@ -876,14 +876,14 @@ func (h *Handler) RequestResetHandler(w http.ResponseWriter, r *http.Request) { var params PasswordResetRequest err := json.NewDecoder(r.Body).Decode(¶ms) if err != nil { - log.Printf("Decoding request failed: %v", err) + h.Logger.Error("Decoding request failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid request body") return } resetJWT, err := user.RequestPasswordReset(h.UserRepo, params.Email) if err != nil { - log.Printf("Error generating reset token for email %s: %v", params.Email, err) + h.Logger.Error("Error generating reset token for email %s: %v", params.Email, err) w.WriteHeader(http.StatusOK) return } @@ -894,7 +894,7 @@ func (h *Handler) RequestResetHandler(w http.ResponseWriter, r *http.Request) { go func(email, resetURL string) { err := h.Mailer.SendPasswordReset(email, resetURL) if err != nil { - log.Printf("SendPasswordReset error: %v", err) + h.Logger.Error("SendPasswordReset error: %v", err) return } }(params.Email, resetURL) @@ -911,13 +911,13 @@ func (h *Handler) ResetPasswordHandler(w http.ResponseWriter, r *http.Request) { var params PasswordResetPayload if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil { - log.Printf("Decoding payload failed: %v", err) + h.Logger.Error("Decoding payload failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid request body") return } err := user.ResetPassword(h.UserRepo, params.NewPassword, params.Token) if err != nil { - log.Printf("ResetPasswordHandler failed: %v", err) + h.Logger.Error("ResetPasswordHandler failed: %v", err) RespondWithError(w, http.StatusUnauthorized, "Invalid or expired token") return } @@ -934,14 +934,14 @@ func (h *Handler) CreateCheckoutSessionHandler(w http.ResponseWriter, r *http.Re userID, ok := r.Context().Value(middleware.ContextKeyTokenParams).(int) if !ok { - log.Printf("r.Context().Value() failed") + h.Logger.Error("r.Context().Value() failed") RespondWithError(w, http.StatusUnauthorized, "Unauthorized") return } var params CheckoutRequest if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil || params.Tier == "" { - log.Printf("jsonNewDecoder failed: %v", err) + h.Logger.Error("jsonNewDecoder failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Missing or invalid tier") return } @@ -967,14 +967,14 @@ func (h *Handler) CreateCheckoutSessionHandler(w http.ResponseWriter, r *http.Re priceIDInt, err := strconv.Atoi(priceID) if err != nil { - log.Printf("strconv.Atoi() failed: %v", err) + h.Logger.Error("strconv.Atoi() failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } url, err := h.Billing.RequestCheckoutSession(user.Email, priceIDInt) if err != nil { - log.Printf("billing.CreateCheckoutSession failed: %v", err) + h.Logger.Error("billing.CreateCheckoutSession failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Could not start checkout") return } @@ -996,14 +996,14 @@ func (h *Handler) CancelSubscriptionHandler(w http.ResponseWriter, r *http.Reque userReturned, err := user.GetUser(h.UserRepo, userID) if err != nil { - log.Printf("GetUser failed: %v", err) + h.Logger.Error("GetUser failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Could not retrieve user") return } err = h.Billing.RequestDeleteSubscription(userReturned.SubscriptionID) if err != nil { - log.Printf("DeleteSubscription failed: %v", err) + h.Logger.Error("DeleteSubscription failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Could not cancel subscription") return } @@ -1025,14 +1025,14 @@ func (h *Handler) ResumeSubscriptionHandler(w http.ResponseWriter, r *http.Reque userReturned, err := user.GetUser(h.UserRepo, userID) if err != nil { - log.Printf("GetUser failed: %v", err) + h.Logger.Error("GetUser failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Could not retrieve user") return } err = h.Billing.RequestResumeSubscription(userReturned.SubscriptionID) if err != nil { - log.Printf("DeleteSubscription failed: %v", err) + h.Logger.Error("DeleteSubscription failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Could not cancel subscription") return } @@ -1054,7 +1054,7 @@ func (h *Handler) ChangePlanHandler(w http.ResponseWriter, r *http.Request) { var params CheckoutRequest if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil || params.Tier == "" { - log.Printf("jsonNewDecoder failed: %v", err) + h.Logger.Error("jsonNewDecoder failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Missing or invalid tier") return } @@ -1078,13 +1078,13 @@ func (h *Handler) ChangePlanHandler(w http.ResponseWriter, r *http.Request) { priceIDInt, err := strconv.Atoi(priceID) if err != nil { - log.Printf("strconv.Atoi() failed: %v", err) + h.Logger.Error("strconv.Atoi() failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } if err := h.Billing.RequestUpdateSubscriptionVariant(user.SubscriptionID, priceIDInt); err != nil { - log.Printf("UpdateLemonSubscriptionVariant failed: %v", err) + h.Logger.Error("UpdateLemonSubscriptionVariant failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update subscription") return } @@ -1100,7 +1100,7 @@ func (h *Handler) BillingWebhookHandler(w http.ResponseWriter, r *http.Request) body, err := io.ReadAll(r.Body) if err != nil { - log.Printf("io.ReadAll failed: %v", err) + h.Logger.Error("io.ReadAll failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Bad Request") return } @@ -1108,7 +1108,7 @@ func (h *Handler) BillingWebhookHandler(w http.ResponseWriter, r *http.Request) signature := r.Header.Get("X-Signature") if !h.Billing.VerifyBillingSignature(signature, body, os.Getenv("LEMON_WEBHOOK_SECRET")) { - log.Printf("Invalid billing event signature") + h.Logger.Error("Invalid billing event signature") RespondWithError(w, http.StatusUnauthorized, "Invalid signature") return } @@ -1116,7 +1116,7 @@ func (h *Handler) BillingWebhookHandler(w http.ResponseWriter, r *http.Request) var webhookPayload billing.BillingWebhookPayload err = json.Unmarshal(body, &webhookPayload) if err != nil { - log.Printf("json.Unmarshal failed: %v", err) + h.Logger.Error("json.Unmarshal failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid JSON") return } @@ -1124,12 +1124,12 @@ func (h *Handler) BillingWebhookHandler(w http.ResponseWriter, r *http.Request) webhookID := webhookPayload.Meta.WebhookID exists, err := h.BillingRepo.HasWebhookBeenProcessed(webhookID) if err != nil { - log.Printf("h.BillingRepo.HasWebhookBeenProcessed failed: %v", err) + h.Logger.Error("h.BillingRepo.HasWebhookBeenProcessed failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Error checking webhook") return } if exists { - log.Printf("Webhook %s already processed", webhookID) + h.Logger.Error("Webhook %s already processed", webhookID) w.WriteHeader(http.StatusOK) return } @@ -1140,91 +1140,91 @@ func (h *Handler) BillingWebhookHandler(w http.ResponseWriter, r *http.Request) eventType := webhookPayload.Meta.EventName - log.Printf("Received webhook: eventType=%q, webhookID=%s, subscriptionID=%s", eventType, webhookID, subscriptionID) + h.Logger.Error("Received webhook: eventType=%q, webhookID=%s, subscriptionID=%s", eventType, webhookID, subscriptionID) switch eventType { case "order_created": var orderAttrs billing.OrderAttributes if err := json.Unmarshal(webhookPayload.Data.Attributes, &orderAttrs); err != nil { - log.Printf("Unmarshal order_created failed: %v", err) + h.Logger.Error("Unmarshal order_created failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid order_created payload") return } err = h.Billing.ApplyCredits(h.UserRepo, h.BillingRepo, orderAttrs.UserEmail, orderAttrs.FirstOrderItem.VariantID) if err != nil { - log.Printf("h.Billing.ApplyCredits failed: %v", err) + h.Logger.Error("h.Billing.ApplyCredits failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } case "subscription_created": var SubCreatedAttrs billing.SubscriptionAttributes if err := json.Unmarshal(webhookPayload.Data.Attributes, &SubCreatedAttrs); err != nil { - log.Printf("Unmarshal subscription_created failed: %v", err) + h.Logger.Error("Unmarshal subscription_created failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid subscription_created payload") return } exists, err := h.UserRepo.HasActiveOrCancelledSubscription(SubCreatedAttrs.UserEmail) if err != nil { - log.Printf("Subscription duplicate check failed: %v", err) + h.Logger.Error("Subscription duplicate check failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Subscription check failed") return } if exists { - log.Printf("Duplicate subscription attempt blocked for %s", SubCreatedAttrs.UserEmail) + h.Logger.Error("Duplicate subscription attempt blocked for %s", SubCreatedAttrs.UserEmail) return } err = h.Billing.CreateSubscription(h.UserRepo, SubCreatedAttrs, subscriptionID) if err != nil { - log.Printf("h.Billing.CreateSubscription failed: %v", err) + h.Logger.Error("h.Billing.CreateSubscription failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } case "subscription_cancelled": if err := json.Unmarshal(webhookPayload.Data.Attributes, &emailAttribute); err != nil { - log.Printf("Unmarshal subscription_cancelled failed: %v", err) + h.Logger.Error("Unmarshal subscription_cancelled failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid subscription_cancelled payload") return } err = h.Billing.CancelSubscription(h.UserRepo, emailAttribute.UserEmail) if err != nil { - log.Printf("h.Billing.CancelSubscription failed: %v", err) + h.Logger.Error("h.Billing.CancelSubscription failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } case "subscription_resumed": if err := json.Unmarshal(webhookPayload.Data.Attributes, &emailAttribute); err != nil { - log.Printf("Unmarshal subscription_resumed failed: %v", err) + h.Logger.Error("Unmarshal subscription_resumed failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid subscription_resumed payload") return } err = h.Billing.ResumeSubscription(h.UserRepo, emailAttribute.UserEmail) if err != nil { - log.Printf("h.Billing.ResumeSubscription failed: %v", err) + h.Logger.Error("h.Billing.ResumeSubscription failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } case "subscription_expired": if err := json.Unmarshal(webhookPayload.Data.Attributes, &emailAttribute); err != nil { - log.Printf("Unmarshal subscription_expired failed: %v", err) + h.Logger.Error("Unmarshal subscription_expired failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid subscription_expired payload") return } err = h.Billing.ExpireSubscription(h.UserRepo, h.BillingRepo, emailAttribute.UserEmail) if err != nil { - log.Printf("h.Billing.ExpireSubscription failed: %v", err) + h.Logger.Error("h.Billing.ExpireSubscription failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } case "subscription_payment_success": var SubRenewAttrs billing.SubscriptionRenewAttributes if err := json.Unmarshal(webhookPayload.Data.Attributes, &SubRenewAttrs); err != nil { - log.Printf("Unmarshal subscription_payment_success failed: %v", err) + h.Logger.Error("Unmarshal subscription_payment_success failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid subscription_payment_success payload") return } @@ -1236,74 +1236,74 @@ func (h *Handler) BillingWebhookHandler(w http.ResponseWriter, r *http.Request) err = h.Billing.RenewSubscription(h.UserRepo, h.BillingRepo, SubRenewAttrs) if err != nil { - log.Printf("h.Billing.RenewSubscription failed: %v", err) + h.Logger.Error("h.Billing.RenewSubscription failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } case "subscription_plan_changed": var SubChangedAttrs billing.SubscriptionAttributes if err := json.Unmarshal(webhookPayload.Data.Attributes, &SubChangedAttrs); err != nil { - log.Printf("Unmarshal subscription_plan_changed failed: %v", err) + h.Logger.Error("Unmarshal subscription_plan_changed failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid subscription_plan_changed payload") return } err = h.Billing.ChangeSubscription(h.UserRepo, h.BillingRepo, SubChangedAttrs) if err != nil { - log.Printf("h.Billing.ChangeSubscription failed: %v", err) + h.Logger.Error("h.Billing.ChangeSubscription failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } case "subscription_updated": var SubChangedAttrs billing.SubscriptionAttributes if err := json.Unmarshal(webhookPayload.Data.Attributes, &SubChangedAttrs); err != nil { - log.Printf("Unmarshal subscription_updated failed: %v", err) + h.Logger.Error("Unmarshal subscription_updated failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid subscription_updated payload") return } err = h.Billing.UpdateSubscription(h.UserRepo, SubChangedAttrs, subscriptionID) if err != nil { - log.Printf("h.Billing.UpdateSubscription failed: %v", err) + h.Logger.Error("h.Billing.UpdateSubscription failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } case "order_refunded": var orderAttrs billing.OrderAttributes if err := json.Unmarshal(webhookPayload.Data.Attributes, &orderAttrs); err != nil { - log.Printf("Unmarshal order_created failed: %v", err) + h.Logger.Error("Unmarshal order_created failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid order_created payload") return } err = h.Billing.DeductCredits(h.UserRepo, h.BillingRepo, orderAttrs) if err != nil { - log.Printf("h.Billing.DeductCredits failed: %v", err) + h.Logger.Error("h.Billing.DeductCredits failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } case "subscription_payment_failed", "subscription_payment_recovered": if err := json.Unmarshal(webhookPayload.Data.Attributes, &emailAttribute); err != nil { - log.Printf("Unmarshal %s failed: %v", eventType, err) + h.Logger.Error("Unmarshal %s failed: %v", eventType, err) RespondWithError(w, http.StatusBadRequest, "Invalid payment status payload") return } - log.Printf("Payment event: %s for user %s", eventType, emailAttribute.UserEmail) + h.Logger.Error("Payment event: %s for user %s", eventType, emailAttribute.UserEmail) default: - log.Printf("Unhandled event type: %s", eventType) + h.Logger.Error("Unhandled event type: %s", eventType) RespondWithError(w, http.StatusNotImplemented, "Unhandled event type") return } if err != nil { - log.Printf("eventType switch func failed: %v", err) + h.Logger.Error("eventType switch func failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } err = h.BillingRepo.MarkWebhookProcessed(webhookID, eventType) if err != nil { - log.Printf("MarkWebhookProcessed failed: %v", err) + h.Logger.Error("MarkWebhookProcessed failed: %v", err) w.WriteHeader(http.StatusOK) return } @@ -1329,7 +1329,7 @@ func (h *Handler) DashboardHandler(w http.ResponseWriter, r *http.Request) { RespondWithError(w, http.StatusUnauthorized, "User not found") return } - log.Printf("dashboard.GetDashboardData failed: %v", err) + h.Logger.Error("dashboard.GetDashboardData failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Could not load dashboard") return } @@ -1355,11 +1355,11 @@ func (h *Handler) JDInputHandler(w http.ResponseWriter, r *http.Request) { if err != nil { var openaiErr *chatgpt.OpenAIError if errors.As(err, &openaiErr) { - log.Printf("OpenAI error: %v", openaiErr) + h.Logger.Error("OpenAI error: %v", openaiErr) RespondWithError(w, openaiErr.StatusCode, openaiErr.Message) return } - log.Printf("chatgpt.ExtractJDInput failed: %v", err) + h.Logger.Error("chatgpt.ExtractJDInput failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to process job description") return } @@ -1368,11 +1368,11 @@ func (h *Handler) JDInputHandler(w http.ResponseWriter, r *http.Request) { if err != nil { var openaiErr *chatgpt.OpenAIError if errors.As(err, &openaiErr) { - log.Printf("OpenAI error: %v", openaiErr) + h.Logger.Error("OpenAI error: %v", openaiErr) RespondWithError(w, openaiErr.StatusCode, openaiErr.Message) return } - log.Printf("chatgpt.ExtractJDSummary failed: %v", err) + h.Logger.Error("chatgpt.ExtractJDSummary failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to process job description") return diff --git a/handlers/model.go b/handlers/model.go index f28abac..be887d3 100644 --- a/handlers/model.go +++ b/handlers/model.go @@ -2,6 +2,7 @@ package handlers import ( "database/sql" + "log/slog" "github.com/michaelboegner/interviewer/billing" "github.com/michaelboegner/interviewer/chatgpt" @@ -61,6 +62,7 @@ type Handler struct { Mailer mailer.MailerClient OpenAI chatgpt.AIClient DB *sql.DB + Logger *slog.Logger } func NewHandler( @@ -72,7 +74,8 @@ func NewHandler( billing *billing.Billing, mailer mailer.MailerClient, openAI chatgpt.AIClient, - db *sql.DB) *Handler { + db *sql.DB, + logger *slog.Logger) *Handler { return &Handler{ InterviewRepo: interviewRepo, UserRepo: userRepo, @@ -83,5 +86,6 @@ func NewHandler( Mailer: mailer, OpenAI: openAI, DB: db, + Logger: logger, } } diff --git a/internal/server/server.go b/internal/server/server.go index d3e9ca8..59e033b 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -23,7 +23,6 @@ type Server struct { } func NewServer(logger *slog.Logger) (*Server, error) { - logger.Info("logger is functioning in server") mux := http.NewServeMux() db, err := database.StartDB() @@ -44,7 +43,7 @@ func NewServer(logger *slog.Logger) (*Server, error) { return nil, err } - handler := handlers.NewHandler(interviewRepo, userRepo, tokenRepo, conversationRepo, billingRepo, billing, mailer, openAI, db) + handler := handlers.NewHandler(interviewRepo, userRepo, tokenRepo, conversationRepo, billingRepo, billing, mailer, openAI, db, logger) mux.Handle("/api/users", http.HandlerFunc(handler.CreateUsersHandler)) mux.Handle("/api/auth/login", http.HandlerFunc(handler.LoginHandler)) From bc914426255a854f1abc4ad06bc2468de1e7e2fc Mon Sep 17 00:00:00 2001 From: MichaelBoegner Date: Thu, 18 Sep 2025 11:52:47 +0700 Subject: [PATCH 02/15] changing log.Printf to slogs --- handlers/handlers.go | 234 +++++++++++++++++++++---------------------- 1 file changed, 117 insertions(+), 117 deletions(-) diff --git a/handlers/handlers.go b/handlers/handlers.go index e337121..a07fd49 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -55,7 +55,7 @@ func (h *Handler) RequestVerificationHandler(w http.ResponseWriter, r *http.Requ verificationJWT, err := user.VerificationToken(req.Email, req.Username, req.Password) if err != nil { - h.Logger.Error("GenerateEmailVerificationToken failed: %v", err) + h.Logger.Error("GenerateEmailVerificationToken failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Failed to create token") return } @@ -63,7 +63,7 @@ func (h *Handler) RequestVerificationHandler(w http.ResponseWriter, r *http.Requ verifyURL := os.Getenv("FRONTEND_URL") + "verify-email?token=" + verificationJWT go func() { if err := h.Mailer.SendVerificationEmail(req.Email, verifyURL); err != nil { - h.Logger.Error("SendVerificationEmail failed: %v", err) + h.Logger.Error("SendVerificationEmail failed", "error", err) } }() @@ -84,7 +84,7 @@ func (h *Handler) CheckEmailHandler(w http.ResponseWriter, r *http.Request) { Email string `json:"email"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - h.Logger.Error("Decoding check-email body failed: %v", err) + h.Logger.Error("Decoding check-email body failed", "error", err) RespondWithError(w, http.StatusBadRequest, "Invalid request body") return } @@ -99,7 +99,7 @@ func (h *Handler) CheckEmailHandler(w http.ResponseWriter, r *http.Request) { RespondWithJSON(w, http.StatusOK, map[string]bool{"exists": false}) return } - h.Logger.Error("CheckEmailHandler internal error: %v", err) + h.Logger.Error("CheckEmailHandler internal error", "error", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } @@ -118,21 +118,21 @@ func (h *Handler) CreateUsersHandler(w http.ResponseWriter, r *http.Request) { } err := json.NewDecoder(r.Body).Decode(&req) if err != nil { - h.Logger.Error("Decoding params failed: %v", err) + h.Logger.Error("Decoding params failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Internal Server Error") return } userCreated, err := user.CreateUser(h.UserRepo, req.Token) if err != nil { - h.Logger.Error("CreateUser error: %v", err) + h.Logger.Error("CreateUser error", "error", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } err = h.Mailer.SendWelcome(userCreated.Email) if err != nil { - h.Logger.Error("h.Mailer.SendWelcome failed: %v", err) + h.Logger.Error("h.Mailer.SendWelcome failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } @@ -159,20 +159,20 @@ func (h *Handler) GetUsersHandler(w http.ResponseWriter, r *http.Request) { userIDParam, err := GetPathID(r, "/api/users/") if err != nil { - h.Logger.Error("GetPathID error: %v\n", err) + h.Logger.Error("GetPathID error", "error", err) RespondWithError(w, http.StatusBadRequest, "UserID required") return } if userID != userIDParam { - h.Logger.Error("UserID mismatch: %v vs. %v", userID, userIDParam) + h.Logger.Error("UserID mismatch", "got", userIDParam, "want", userID) RespondWithError(w, http.StatusUnauthorized, "Invalid ID") return } userReturned, err := user.GetUser(h.UserRepo, userID) if err != nil { - h.Logger.Error("GetUsers error: %v", err) + h.Logger.Error("GetUsers error", "error", err) return } @@ -199,48 +199,48 @@ func (h *Handler) DeleteUserHandler(w http.ResponseWriter, r *http.Request) { userIDParam, err := GetPathID(r, "/api/users/delete/") if err != nil { - h.Logger.Error("GetPathID error: %v\n", err) + h.Logger.Error("GetPathID error", "error", err) RespondWithError(w, http.StatusBadRequest, "UserID required") return } if userID != userIDParam { - h.Logger.Error("UserID mismatch: %v vs. %v", userID, userIDParam) + h.Logger.Error("UserID mismatch", "got", userIDParam, "want", userID) RespondWithError(w, http.StatusUnauthorized, "Invalid ID") return } userReturned, err := user.GetUser(h.UserRepo, userID) if err != nil { - h.Logger.Error("GetUser error: %v", err) + h.Logger.Error("GetUser error", "error", err) RespondWithError(w, http.StatusInternalServerError, "Failed to find user") return } err = h.Billing.CancelSubscription(h.UserRepo, userReturned.Email) if err != nil { - h.Logger.Error("h.Billing.CancelSubscription failed: %v", err) + h.Logger.Error("h.Billing.CancelSubscription failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } err = user.MarkUserDeleted(h.UserRepo, userID) if err != nil { - h.Logger.Error("DeleteUser failed: %v", err) + h.Logger.Error("DeleteUser failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Failed to delete user") return } err = token.DeleteRefreshToken(h.TokenRepo, userID) if err != nil { - h.Logger.Error("DeleteRefreshTokensForUser failed: %v", err) + h.Logger.Error("DeleteRefreshTokensForUser failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } err = h.Mailer.SendDeletionConfirmation(userReturned.Email) if err != nil { - h.Logger.Error("h.Mailer.SendDeletionConfirmation failed: %v", err) + h.Logger.Error("h.Mailer.SendDeletionConfirmation failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } @@ -257,7 +257,7 @@ func (h *Handler) LoginHandler(w http.ResponseWriter, r *http.Request) { params := &middleware.AcceptedVals{} err := json.NewDecoder(r.Body).Decode(params) if err != nil { - h.Logger.Error("Decoding params failed: %v", err) + h.Logger.Error("Decoding params failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Internal Server Error") return } @@ -271,7 +271,7 @@ func (h *Handler) LoginHandler(w http.ResponseWriter, r *http.Request) { jwToken, username, userID, err := user.LoginUser(h.UserRepo, params.Email, params.Password) if err != nil { - h.Logger.Error("LoginUser error: %v", err) + h.Logger.Error("LoginUser error", "error", err) if errors.Is(err, user.ErrAccountDeleted) { RespondWithError(w, http.StatusUnauthorized, user.ErrAccountDeleted.Error()) return @@ -282,7 +282,7 @@ func (h *Handler) LoginHandler(w http.ResponseWriter, r *http.Request) { refreshToken, err := token.CreateRefreshToken(h.TokenRepo, userID) if err != nil { - h.Logger.Error("RefreshToken error: %v", err) + h.Logger.Error("RefreshToken error", "error", err) RespondWithError(w, http.StatusUnauthorized, "") return } @@ -344,14 +344,14 @@ func (h *Handler) GithubLoginHandler(w http.ResponseWriter, r *http.Request) { client = &http.Client{} req, err = http.NewRequest("GET", "https://api.github.com/user", nil) if err != nil { - h.Logger.Error("http.NewRequest failed: %v", err) + log.Printf("http.NewRequest failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } req.Header.Set("Authorization", "Bearer "+tokenResp.AccessToken) githubResp, err := client.Do(req) if err != nil { - h.Logger.Error("GET api.github.com/user failed: %v", err) + log.Printf("GET api.github.com/user failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } @@ -366,14 +366,14 @@ func (h *Handler) GithubLoginHandler(w http.ResponseWriter, r *http.Request) { if githubUser.Email == "" { req, err := http.NewRequest("GET", "https://api.github.com/user/emails", nil) if err != nil { - h.Logger.Error("http.NewRequest failed: %v", err) + log.Printf("http.NewRequest failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } req.Header.Set("Authorization", "Bearer "+tokenResp.AccessToken) emailResp, err := client.Do(req) if err != nil { - h.Logger.Error("GET api.github.com/user/emails failed: %v", err) + log.Printf("GET api.github.com/user/emails failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } @@ -395,7 +395,7 @@ func (h *Handler) GithubLoginHandler(w http.ResponseWriter, r *http.Request) { } if githubUser.Email == "" { - h.Logger.Error("GitHub login failed: no verified email found for user %s", githubUser.Login) + log.Printf("GitHub login failed: no verified email found for user %s", githubUser.Login) RespondWithError(w, http.StatusUnauthorized, "We couldn’t retrieve a valid email address from GitHub. Please check your GitHub email settings and try again.") return } @@ -408,13 +408,13 @@ func (h *Handler) GithubLoginHandler(w http.ResponseWriter, r *http.Request) { jwt, err := token.CreateJWT(strconv.Itoa(user.ID), 0) if err != nil { - h.Logger.Error("token.CreateJWT failed: %v", err) + log.Printf("token.CreateJWT failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } refreshToken, err := token.CreateRefreshToken(h.TokenRepo, user.ID) if err != nil { - h.Logger.Error("token.CreateRefreshToken failed: %v", err) + log.Printf("token.CreateRefreshToken failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } @@ -443,48 +443,48 @@ func (h *Handler) RefreshTokensHandler(w http.ResponseWriter, r *http.Request) { err := json.NewDecoder(r.Body).Decode(params) if err != nil { - h.Logger.Error("Decoding params failed: %v", err) + log.Printf("Decoding params failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal Server Error") return } if params.UserID == 0 { - h.Logger.Error("Invalid userID") + log.Printf("Invalid userID") RespondWithError(w, http.StatusBadRequest, "Invalid username or password") return } storedToken, err := token.GetStoredRefreshToken(h.TokenRepo, params.UserID) if err != nil { - h.Logger.Error("GetStoredRefreshToken error: %v", err) + log.Printf("GetStoredRefreshToken error: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid user_id") return } ok := token.VerifyRefreshToken(storedToken, providedToken) if !ok { - h.Logger.Error("VerifyRefreshToken error") + log.Printf("VerifyRefreshToken error") RespondWithError(w, http.StatusUnauthorized, "Refresh token is invalid") return } refreshToken, err := token.CreateRefreshToken(h.TokenRepo, params.UserID) if err != nil { - h.Logger.Error("CreateRefreshToken error: %v", err) + log.Printf("CreateRefreshToken error: %v", err) RespondWithError(w, http.StatusUnauthorized, "") return } user, err := h.UserRepo.GetUser(params.UserID) if err != nil || user.AccountStatus == "deleted" { - h.Logger.Error("Refresh attempt for deleted account ID %d", params.UserID) + log.Printf("Refresh attempt for deleted account ID %d", params.UserID) RespondWithError(w, http.StatusUnauthorized, "Account deactivated") return } jwToken, err := token.CreateJWT(strconv.Itoa(params.UserID), 0) if err != nil { - h.Logger.Error("JWT creation failed: %v", err) + log.Printf("JWT creation failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "") return } @@ -512,14 +512,14 @@ func (h *Handler) InterviewsHandler(w http.ResponseWriter, r *http.Request) { params := &middleware.AcceptedVals{} err := json.NewDecoder(r.Body).Decode(params) if err != nil { - h.Logger.Error("Decoding params failed: %v", err) + log.Printf("Decoding params failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal Server Error") return } userReturned, err := user.GetUser(h.UserRepo, userID) if err != nil { - h.Logger.Error("GetUser error: %v", err) + log.Printf("GetUser error: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to find user") return } @@ -537,7 +537,7 @@ func (h *Handler) InterviewsHandler(w http.ResponseWriter, r *http.Request) { if err != nil { var openaiErr *chatgpt.OpenAIError if errors.As(err, &openaiErr) { - h.Logger.Error("OpenAI error: %v", openaiErr) + log.Printf("OpenAI error: %v", openaiErr) RespondWithError(w, openaiErr.StatusCode, openaiErr.Message) return } @@ -545,21 +545,21 @@ func (h *Handler) InterviewsHandler(w http.ResponseWriter, r *http.Request) { RespondWithError(w, http.StatusPaymentRequired, "You do not have enough credits to start a new interview or your subscription has expired.") return } - h.Logger.Error("Interview failed to start: %v", err) + log.Printf("Interview failed to start: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to start interview.") return } conversationID, err := conversation.CreateEmptyConversation(h.ConversationRepo, interviewStarted.Id, interviewStarted.Subtopic) if err != nil { - h.Logger.Error("conversation.CreateEmptyConversation failed: %v", err) + log.Printf("conversation.CreateEmptyConversation failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } err = interview.LinkConversation(h.InterviewRepo, interviewStarted.Id, conversationID) if err != nil { - h.Logger.Error("interview.LinkConversation failed: %v", err) + log.Printf("interview.LinkConversation failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } @@ -587,19 +587,19 @@ func (h *Handler) GetInterviewHandler(w http.ResponseWriter, r *http.Request) { interviewID, err := GetPathID(r, "/api/interviews/") if err != nil { - h.Logger.Error("GetPathID failed: %v", err) + log.Printf("GetPathID failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid interview ID") return } interviewReturned, err := interview.GetInterview(h.InterviewRepo, interviewID) if err != nil { - h.Logger.Error("GetInterview failed: %v", err) + log.Printf("GetInterview failed: %v", err) RespondWithError(w, http.StatusNotFound, "Interview not found") return } if interviewReturned.UserId != userID { - h.Logger.Error("User ID mismatch on interview fetch") + log.Printf("User ID mismatch on interview fetch") RespondWithError(w, http.StatusUnauthorized, "Unauthorized") return } @@ -639,21 +639,21 @@ func (h *Handler) UpdateInterviewStatusHandler(w http.ResponseWriter, r *http.Re interviewReturned, err := interview.GetInterview(h.InterviewRepo, interviewID) if err != nil { - h.Logger.Error("GetInterview error: %v\n", err) + log.Printf("GetInterview error: %v\n", err) RespondWithError(w, http.StatusBadRequest, "Invalid ID") return } err = ValidateInterviewStatusTransition(interviewReturned.Status, payload.Status) if err != nil { - h.Logger.Error("ValidateInterviewStatusTransition failed: %v", err) + log.Printf("ValidateInterviewStatusTransition failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid status transition") return } err = h.InterviewRepo.UpdateStatus(interviewID, userID, payload.Status) if err != nil { - h.Logger.Error("UpdateInterviewStatus failed: %v", err) + log.Printf("UpdateInterviewStatus failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Could not update status") return } @@ -676,7 +676,7 @@ func (h *Handler) CreateConversationsHandler(w http.ResponseWriter, r *http.Requ params := &middleware.AcceptedVals{} err := json.NewDecoder(r.Body).Decode(params) if err != nil { - h.Logger.Error("Decoding params failed: %v", err) + log.Printf("Decoding params failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal Server Error") return } @@ -684,19 +684,19 @@ func (h *Handler) CreateConversationsHandler(w http.ResponseWriter, r *http.Requ interviewID, err := GetPathID(r, "/api/conversations/create/") if err != nil { - h.Logger.Error("PathID error: %v\n", err) + log.Printf("PathID error: %v\n", err) RespondWithError(w, http.StatusBadRequest, "Missing ID") return } interviewReturned, err := interview.GetInterview(h.InterviewRepo, interviewID) if err != nil { - h.Logger.Error("GetInterview error: %v\n", err) + log.Printf("GetInterview error: %v\n", err) RespondWithError(w, http.StatusBadRequest, "Invalid ID") return } if interviewReturned.UserId != userID { - h.Logger.Error("interview.userid != token user_id") + log.Printf("interview.userid != token user_id") RespondWithError(w, http.StatusUnauthorized, "Unauthorized") return } @@ -707,7 +707,7 @@ func (h *Handler) CreateConversationsHandler(w http.ResponseWriter, r *http.Requ conversationReturned, err := conversation.GetConversation(h.ConversationRepo, interviewID) if err != nil { - h.Logger.Error("conversation.GetConversation failed: %v", err) + log.Printf("conversation.GetConversation failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid ID") return } @@ -725,11 +725,11 @@ func (h *Handler) CreateConversationsHandler(w http.ResponseWriter, r *http.Requ if err != nil { var openaiErr *chatgpt.OpenAIError if errors.As(err, &openaiErr) { - h.Logger.Error("OpenAI error: %v", openaiErr) + log.Printf("OpenAI error: %v", openaiErr) RespondWithError(w, openaiErr.StatusCode, openaiErr.Message) return } - h.Logger.Error("CreateConversation error: %v", err) + log.Printf("CreateConversation error: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid interview_id") return } @@ -755,32 +755,32 @@ func (h *Handler) AppendConversationsHandler(w http.ResponseWriter, r *http.Requ params := &middleware.AcceptedVals{} err := json.NewDecoder(r.Body).Decode(params) if err != nil { - h.Logger.Error("Decoding params failed: %v", err) + log.Printf("Decoding params failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal Server Error") return } if params.Message == "" { - h.Logger.Error("messageUserResponse is nil") + log.Printf("messageUserResponse is nil") RespondWithError(w, http.StatusBadRequest, "Missing message") return } interviewID, err := GetPathID(r, "/api/conversations/append/") if err != nil { - h.Logger.Error("PathID error: %v\n", err) + log.Printf("PathID error: %v\n", err) RespondWithError(w, http.StatusBadRequest, "Missing ID") return } interviewReturned, err := interview.GetInterview(h.InterviewRepo, interviewID) if err != nil { - h.Logger.Error("GetInterview error: %v\n", err) + log.Printf("GetInterview error: %v\n", err) RespondWithError(w, http.StatusBadRequest, "Invalid ID") return } if interviewReturned.UserId != userID { - h.Logger.Error("interview.userid != token user_id") + log.Printf("interview.userid != token user_id") RespondWithError(w, http.StatusUnauthorized, "Unauthorized") return } @@ -791,7 +791,7 @@ func (h *Handler) AppendConversationsHandler(w http.ResponseWriter, r *http.Requ conversationReturned, err := conversation.GetConversation(h.ConversationRepo, interviewID) if err != nil { - h.Logger.Error("GetConversation error: %v", err) + log.Printf("GetConversation error: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid ID.") return } @@ -808,11 +808,11 @@ func (h *Handler) AppendConversationsHandler(w http.ResponseWriter, r *http.Requ if err != nil { var openaiErr *chatgpt.OpenAIError if errors.As(err, &openaiErr) { - h.Logger.Error("OpenAI error: %v", openaiErr) + log.Printf("OpenAI error: %v", openaiErr) RespondWithError(w, openaiErr.StatusCode, openaiErr.Message) return } - h.Logger.Error("AppendConversation error: %v", err) + log.Printf("AppendConversation error: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid ID.") return } @@ -837,26 +837,26 @@ func (h *Handler) GetConversationHandler(w http.ResponseWriter, r *http.Request) interviewID, err := GetPathID(r, "/api/conversations/") if err != nil { - h.Logger.Error("PathID error: %v\n", err) + log.Printf("PathID error: %v\n", err) RespondWithError(w, http.StatusBadRequest, "Missing ID") return } interviewReturned, err := interview.GetInterview(h.InterviewRepo, interviewID) if err != nil { - h.Logger.Error("GetInterview error: %v\n", err) + log.Printf("GetInterview error: %v\n", err) RespondWithError(w, http.StatusBadRequest, "Invalid ID") return } if interviewReturned.UserId != userID { - h.Logger.Error("interview.userid != token user_id") + log.Printf("interview.userid != token user_id") RespondWithError(w, http.StatusUnauthorized, "Unauthorized") return } conversationReturned, err := conversation.GetConversation(h.ConversationRepo, interviewID) if err != nil { - h.Logger.Error("GetConversation error: %v", err) + log.Printf("GetConversation error: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid ID.") return } @@ -876,14 +876,14 @@ func (h *Handler) RequestResetHandler(w http.ResponseWriter, r *http.Request) { var params PasswordResetRequest err := json.NewDecoder(r.Body).Decode(¶ms) if err != nil { - h.Logger.Error("Decoding request failed: %v", err) + log.Printf("Decoding request failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid request body") return } resetJWT, err := user.RequestPasswordReset(h.UserRepo, params.Email) if err != nil { - h.Logger.Error("Error generating reset token for email %s: %v", params.Email, err) + log.Printf("Error generating reset token for email %s: %v", params.Email, err) w.WriteHeader(http.StatusOK) return } @@ -894,7 +894,7 @@ func (h *Handler) RequestResetHandler(w http.ResponseWriter, r *http.Request) { go func(email, resetURL string) { err := h.Mailer.SendPasswordReset(email, resetURL) if err != nil { - h.Logger.Error("SendPasswordReset error: %v", err) + log.Printf("SendPasswordReset error: %v", err) return } }(params.Email, resetURL) @@ -911,13 +911,13 @@ func (h *Handler) ResetPasswordHandler(w http.ResponseWriter, r *http.Request) { var params PasswordResetPayload if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil { - h.Logger.Error("Decoding payload failed: %v", err) + log.Printf("Decoding payload failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid request body") return } err := user.ResetPassword(h.UserRepo, params.NewPassword, params.Token) if err != nil { - h.Logger.Error("ResetPasswordHandler failed: %v", err) + log.Printf("ResetPasswordHandler failed: %v", err) RespondWithError(w, http.StatusUnauthorized, "Invalid or expired token") return } @@ -934,14 +934,14 @@ func (h *Handler) CreateCheckoutSessionHandler(w http.ResponseWriter, r *http.Re userID, ok := r.Context().Value(middleware.ContextKeyTokenParams).(int) if !ok { - h.Logger.Error("r.Context().Value() failed") + log.Printf("r.Context().Value() failed") RespondWithError(w, http.StatusUnauthorized, "Unauthorized") return } var params CheckoutRequest if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil || params.Tier == "" { - h.Logger.Error("jsonNewDecoder failed: %v", err) + log.Printf("jsonNewDecoder failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Missing or invalid tier") return } @@ -967,14 +967,14 @@ func (h *Handler) CreateCheckoutSessionHandler(w http.ResponseWriter, r *http.Re priceIDInt, err := strconv.Atoi(priceID) if err != nil { - h.Logger.Error("strconv.Atoi() failed: %v", err) + log.Printf("strconv.Atoi() failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } url, err := h.Billing.RequestCheckoutSession(user.Email, priceIDInt) if err != nil { - h.Logger.Error("billing.CreateCheckoutSession failed: %v", err) + log.Printf("billing.CreateCheckoutSession failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Could not start checkout") return } @@ -996,14 +996,14 @@ func (h *Handler) CancelSubscriptionHandler(w http.ResponseWriter, r *http.Reque userReturned, err := user.GetUser(h.UserRepo, userID) if err != nil { - h.Logger.Error("GetUser failed: %v", err) + log.Printf("GetUser failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Could not retrieve user") return } err = h.Billing.RequestDeleteSubscription(userReturned.SubscriptionID) if err != nil { - h.Logger.Error("DeleteSubscription failed: %v", err) + log.Printf("DeleteSubscription failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Could not cancel subscription") return } @@ -1025,14 +1025,14 @@ func (h *Handler) ResumeSubscriptionHandler(w http.ResponseWriter, r *http.Reque userReturned, err := user.GetUser(h.UserRepo, userID) if err != nil { - h.Logger.Error("GetUser failed: %v", err) + log.Printf("GetUser failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Could not retrieve user") return } err = h.Billing.RequestResumeSubscription(userReturned.SubscriptionID) if err != nil { - h.Logger.Error("DeleteSubscription failed: %v", err) + log.Printf("DeleteSubscription failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Could not cancel subscription") return } @@ -1054,7 +1054,7 @@ func (h *Handler) ChangePlanHandler(w http.ResponseWriter, r *http.Request) { var params CheckoutRequest if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil || params.Tier == "" { - h.Logger.Error("jsonNewDecoder failed: %v", err) + log.Printf("jsonNewDecoder failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Missing or invalid tier") return } @@ -1078,13 +1078,13 @@ func (h *Handler) ChangePlanHandler(w http.ResponseWriter, r *http.Request) { priceIDInt, err := strconv.Atoi(priceID) if err != nil { - h.Logger.Error("strconv.Atoi() failed: %v", err) + log.Printf("strconv.Atoi() failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } if err := h.Billing.RequestUpdateSubscriptionVariant(user.SubscriptionID, priceIDInt); err != nil { - h.Logger.Error("UpdateLemonSubscriptionVariant failed: %v", err) + log.Printf("UpdateLemonSubscriptionVariant failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update subscription") return } @@ -1100,7 +1100,7 @@ func (h *Handler) BillingWebhookHandler(w http.ResponseWriter, r *http.Request) body, err := io.ReadAll(r.Body) if err != nil { - h.Logger.Error("io.ReadAll failed: %v", err) + log.Printf("io.ReadAll failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Bad Request") return } @@ -1108,7 +1108,7 @@ func (h *Handler) BillingWebhookHandler(w http.ResponseWriter, r *http.Request) signature := r.Header.Get("X-Signature") if !h.Billing.VerifyBillingSignature(signature, body, os.Getenv("LEMON_WEBHOOK_SECRET")) { - h.Logger.Error("Invalid billing event signature") + log.Printf("Invalid billing event signature") RespondWithError(w, http.StatusUnauthorized, "Invalid signature") return } @@ -1116,7 +1116,7 @@ func (h *Handler) BillingWebhookHandler(w http.ResponseWriter, r *http.Request) var webhookPayload billing.BillingWebhookPayload err = json.Unmarshal(body, &webhookPayload) if err != nil { - h.Logger.Error("json.Unmarshal failed: %v", err) + log.Printf("json.Unmarshal failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid JSON") return } @@ -1124,12 +1124,12 @@ func (h *Handler) BillingWebhookHandler(w http.ResponseWriter, r *http.Request) webhookID := webhookPayload.Meta.WebhookID exists, err := h.BillingRepo.HasWebhookBeenProcessed(webhookID) if err != nil { - h.Logger.Error("h.BillingRepo.HasWebhookBeenProcessed failed: %v", err) + log.Printf("h.BillingRepo.HasWebhookBeenProcessed failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Error checking webhook") return } if exists { - h.Logger.Error("Webhook %s already processed", webhookID) + log.Printf("Webhook %s already processed", webhookID) w.WriteHeader(http.StatusOK) return } @@ -1140,91 +1140,91 @@ func (h *Handler) BillingWebhookHandler(w http.ResponseWriter, r *http.Request) eventType := webhookPayload.Meta.EventName - h.Logger.Error("Received webhook: eventType=%q, webhookID=%s, subscriptionID=%s", eventType, webhookID, subscriptionID) + log.Printf("Received webhook: eventType=%q, webhookID=%s, subscriptionID=%s", eventType, webhookID, subscriptionID) switch eventType { case "order_created": var orderAttrs billing.OrderAttributes if err := json.Unmarshal(webhookPayload.Data.Attributes, &orderAttrs); err != nil { - h.Logger.Error("Unmarshal order_created failed: %v", err) + log.Printf("Unmarshal order_created failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid order_created payload") return } err = h.Billing.ApplyCredits(h.UserRepo, h.BillingRepo, orderAttrs.UserEmail, orderAttrs.FirstOrderItem.VariantID) if err != nil { - h.Logger.Error("h.Billing.ApplyCredits failed: %v", err) + log.Printf("h.Billing.ApplyCredits failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } case "subscription_created": var SubCreatedAttrs billing.SubscriptionAttributes if err := json.Unmarshal(webhookPayload.Data.Attributes, &SubCreatedAttrs); err != nil { - h.Logger.Error("Unmarshal subscription_created failed: %v", err) + log.Printf("Unmarshal subscription_created failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid subscription_created payload") return } exists, err := h.UserRepo.HasActiveOrCancelledSubscription(SubCreatedAttrs.UserEmail) if err != nil { - h.Logger.Error("Subscription duplicate check failed: %v", err) + log.Printf("Subscription duplicate check failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Subscription check failed") return } if exists { - h.Logger.Error("Duplicate subscription attempt blocked for %s", SubCreatedAttrs.UserEmail) + log.Printf("Duplicate subscription attempt blocked for %s", SubCreatedAttrs.UserEmail) return } err = h.Billing.CreateSubscription(h.UserRepo, SubCreatedAttrs, subscriptionID) if err != nil { - h.Logger.Error("h.Billing.CreateSubscription failed: %v", err) + log.Printf("h.Billing.CreateSubscription failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } case "subscription_cancelled": if err := json.Unmarshal(webhookPayload.Data.Attributes, &emailAttribute); err != nil { - h.Logger.Error("Unmarshal subscription_cancelled failed: %v", err) + log.Printf("Unmarshal subscription_cancelled failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid subscription_cancelled payload") return } err = h.Billing.CancelSubscription(h.UserRepo, emailAttribute.UserEmail) if err != nil { - h.Logger.Error("h.Billing.CancelSubscription failed: %v", err) + log.Printf("h.Billing.CancelSubscription failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } case "subscription_resumed": if err := json.Unmarshal(webhookPayload.Data.Attributes, &emailAttribute); err != nil { - h.Logger.Error("Unmarshal subscription_resumed failed: %v", err) + log.Printf("Unmarshal subscription_resumed failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid subscription_resumed payload") return } err = h.Billing.ResumeSubscription(h.UserRepo, emailAttribute.UserEmail) if err != nil { - h.Logger.Error("h.Billing.ResumeSubscription failed: %v", err) + log.Printf("h.Billing.ResumeSubscription failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } case "subscription_expired": if err := json.Unmarshal(webhookPayload.Data.Attributes, &emailAttribute); err != nil { - h.Logger.Error("Unmarshal subscription_expired failed: %v", err) + log.Printf("Unmarshal subscription_expired failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid subscription_expired payload") return } err = h.Billing.ExpireSubscription(h.UserRepo, h.BillingRepo, emailAttribute.UserEmail) if err != nil { - h.Logger.Error("h.Billing.ExpireSubscription failed: %v", err) + log.Printf("h.Billing.ExpireSubscription failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } case "subscription_payment_success": var SubRenewAttrs billing.SubscriptionRenewAttributes if err := json.Unmarshal(webhookPayload.Data.Attributes, &SubRenewAttrs); err != nil { - h.Logger.Error("Unmarshal subscription_payment_success failed: %v", err) + log.Printf("Unmarshal subscription_payment_success failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid subscription_payment_success payload") return } @@ -1236,74 +1236,74 @@ func (h *Handler) BillingWebhookHandler(w http.ResponseWriter, r *http.Request) err = h.Billing.RenewSubscription(h.UserRepo, h.BillingRepo, SubRenewAttrs) if err != nil { - h.Logger.Error("h.Billing.RenewSubscription failed: %v", err) + log.Printf("h.Billing.RenewSubscription failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } case "subscription_plan_changed": var SubChangedAttrs billing.SubscriptionAttributes if err := json.Unmarshal(webhookPayload.Data.Attributes, &SubChangedAttrs); err != nil { - h.Logger.Error("Unmarshal subscription_plan_changed failed: %v", err) + log.Printf("Unmarshal subscription_plan_changed failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid subscription_plan_changed payload") return } err = h.Billing.ChangeSubscription(h.UserRepo, h.BillingRepo, SubChangedAttrs) if err != nil { - h.Logger.Error("h.Billing.ChangeSubscription failed: %v", err) + log.Printf("h.Billing.ChangeSubscription failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } case "subscription_updated": var SubChangedAttrs billing.SubscriptionAttributes if err := json.Unmarshal(webhookPayload.Data.Attributes, &SubChangedAttrs); err != nil { - h.Logger.Error("Unmarshal subscription_updated failed: %v", err) + log.Printf("Unmarshal subscription_updated failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid subscription_updated payload") return } err = h.Billing.UpdateSubscription(h.UserRepo, SubChangedAttrs, subscriptionID) if err != nil { - h.Logger.Error("h.Billing.UpdateSubscription failed: %v", err) + log.Printf("h.Billing.UpdateSubscription failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } case "order_refunded": var orderAttrs billing.OrderAttributes if err := json.Unmarshal(webhookPayload.Data.Attributes, &orderAttrs); err != nil { - h.Logger.Error("Unmarshal order_created failed: %v", err) + log.Printf("Unmarshal order_created failed: %v", err) RespondWithError(w, http.StatusBadRequest, "Invalid order_created payload") return } err = h.Billing.DeductCredits(h.UserRepo, h.BillingRepo, orderAttrs) if err != nil { - h.Logger.Error("h.Billing.DeductCredits failed: %v", err) + log.Printf("h.Billing.DeductCredits failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } case "subscription_payment_failed", "subscription_payment_recovered": if err := json.Unmarshal(webhookPayload.Data.Attributes, &emailAttribute); err != nil { - h.Logger.Error("Unmarshal %s failed: %v", eventType, err) + log.Printf("Unmarshal %s failed: %v", eventType, err) RespondWithError(w, http.StatusBadRequest, "Invalid payment status payload") return } - h.Logger.Error("Payment event: %s for user %s", eventType, emailAttribute.UserEmail) + log.Printf("Payment event: %s for user %s", eventType, emailAttribute.UserEmail) default: - h.Logger.Error("Unhandled event type: %s", eventType) + log.Printf("Unhandled event type: %s", eventType) RespondWithError(w, http.StatusNotImplemented, "Unhandled event type") return } if err != nil { - h.Logger.Error("eventType switch func failed: %v", err) + log.Printf("eventType switch func failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } err = h.BillingRepo.MarkWebhookProcessed(webhookID, eventType) if err != nil { - h.Logger.Error("MarkWebhookProcessed failed: %v", err) + log.Printf("MarkWebhookProcessed failed: %v", err) w.WriteHeader(http.StatusOK) return } @@ -1329,7 +1329,7 @@ func (h *Handler) DashboardHandler(w http.ResponseWriter, r *http.Request) { RespondWithError(w, http.StatusUnauthorized, "User not found") return } - h.Logger.Error("dashboard.GetDashboardData failed: %v", err) + log.Printf("dashboard.GetDashboardData failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Could not load dashboard") return } @@ -1355,11 +1355,11 @@ func (h *Handler) JDInputHandler(w http.ResponseWriter, r *http.Request) { if err != nil { var openaiErr *chatgpt.OpenAIError if errors.As(err, &openaiErr) { - h.Logger.Error("OpenAI error: %v", openaiErr) + log.Printf("OpenAI error: %v", openaiErr) RespondWithError(w, openaiErr.StatusCode, openaiErr.Message) return } - h.Logger.Error("chatgpt.ExtractJDInput failed: %v", err) + log.Printf("chatgpt.ExtractJDInput failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to process job description") return } @@ -1368,11 +1368,11 @@ func (h *Handler) JDInputHandler(w http.ResponseWriter, r *http.Request) { if err != nil { var openaiErr *chatgpt.OpenAIError if errors.As(err, &openaiErr) { - h.Logger.Error("OpenAI error: %v", openaiErr) + log.Printf("OpenAI error: %v", openaiErr) RespondWithError(w, openaiErr.StatusCode, openaiErr.Message) return } - h.Logger.Error("chatgpt.ExtractJDSummary failed: %v", err) + log.Printf("chatgpt.ExtractJDSummary failed: %v", err) RespondWithError(w, http.StatusInternalServerError, "Failed to process job description") return From 0c7653d50d708ee050ffe4ef3be0158512f33957 Mon Sep 17 00:00:00 2001 From: MichaelBoegner Date: Fri, 19 Sep 2025 12:12:56 +0700 Subject: [PATCH 03/15] changed out log.Printfs for slog Errors and Infos --- handlers/handlers.go | 204 ++++++++++++++++++++++--------------------- handlers/helpers.go | 2 +- 2 files changed, 105 insertions(+), 101 deletions(-) diff --git a/handlers/handlers.go b/handlers/handlers.go index a07fd49..fab1bdf 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -5,7 +5,6 @@ import ( "encoding/json" "errors" "io" - "log" "net/http" "net/url" "os" @@ -344,14 +343,14 @@ func (h *Handler) GithubLoginHandler(w http.ResponseWriter, r *http.Request) { client = &http.Client{} req, err = http.NewRequest("GET", "https://api.github.com/user", nil) if err != nil { - log.Printf("http.NewRequest failed: %v", err) + h.Logger.Error("http.NewRequest failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } req.Header.Set("Authorization", "Bearer "+tokenResp.AccessToken) githubResp, err := client.Do(req) if err != nil { - log.Printf("GET api.github.com/user failed: %v", err) + h.Logger.Error("GET api.github.com/user failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } @@ -366,14 +365,14 @@ func (h *Handler) GithubLoginHandler(w http.ResponseWriter, r *http.Request) { if githubUser.Email == "" { req, err := http.NewRequest("GET", "https://api.github.com/user/emails", nil) if err != nil { - log.Printf("http.NewRequest failed: %v", err) + h.Logger.Error("http.NewRequest failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } req.Header.Set("Authorization", "Bearer "+tokenResp.AccessToken) emailResp, err := client.Do(req) if err != nil { - log.Printf("GET api.github.com/user/emails failed: %v", err) + h.Logger.Error("GET api.github.com/user/emails failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } @@ -395,7 +394,7 @@ func (h *Handler) GithubLoginHandler(w http.ResponseWriter, r *http.Request) { } if githubUser.Email == "" { - log.Printf("GitHub login failed: no verified email found for user %s", githubUser.Login) + h.Logger.Error("GitHub login failed: no verified email found for user", "userEmail", githubUser.Login) RespondWithError(w, http.StatusUnauthorized, "We couldn’t retrieve a valid email address from GitHub. Please check your GitHub email settings and try again.") return } @@ -408,13 +407,13 @@ func (h *Handler) GithubLoginHandler(w http.ResponseWriter, r *http.Request) { jwt, err := token.CreateJWT(strconv.Itoa(user.ID), 0) if err != nil { - log.Printf("token.CreateJWT failed: %v", err) + h.Logger.Error("token.CreateJWT failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } refreshToken, err := token.CreateRefreshToken(h.TokenRepo, user.ID) if err != nil { - log.Printf("token.CreateRefreshToken failed: %v", err) + h.Logger.Error("token.CreateRefreshToken failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } @@ -443,48 +442,53 @@ func (h *Handler) RefreshTokensHandler(w http.ResponseWriter, r *http.Request) { err := json.NewDecoder(r.Body).Decode(params) if err != nil { - log.Printf("Decoding params failed: %v", err) + h.Logger.Error("Decoding params failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Internal Server Error") return } if params.UserID == 0 { - log.Printf("Invalid userID") + h.Logger.Error("Invalid userID") RespondWithError(w, http.StatusBadRequest, "Invalid username or password") return } storedToken, err := token.GetStoredRefreshToken(h.TokenRepo, params.UserID) if err != nil { - log.Printf("GetStoredRefreshToken error: %v", err) + h.Logger.Error("GetStoredRefreshToken error", "error", err) RespondWithError(w, http.StatusBadRequest, "Invalid user_id") return } ok := token.VerifyRefreshToken(storedToken, providedToken) if !ok { - log.Printf("VerifyRefreshToken error") + h.Logger.Error("VerifyRefreshToken error") RespondWithError(w, http.StatusUnauthorized, "Refresh token is invalid") return } refreshToken, err := token.CreateRefreshToken(h.TokenRepo, params.UserID) if err != nil { - log.Printf("CreateRefreshToken error: %v", err) + h.Logger.Error("CreateRefreshToken error", "error", err) RespondWithError(w, http.StatusUnauthorized, "") return } user, err := h.UserRepo.GetUser(params.UserID) - if err != nil || user.AccountStatus == "deleted" { - log.Printf("Refresh attempt for deleted account ID %d", params.UserID) + if err != nil { + h.Logger.Error("h.UserRepo.GetUser error", "error", err) + RespondWithError(w, http.StatusUnauthorized, "Account deactivated") + return + } + if user.AccountStatus == "deleted" { + h.Logger.Error("Refresh attempt for deleted account ID", "userID", params.UserID) RespondWithError(w, http.StatusUnauthorized, "Account deactivated") return } jwToken, err := token.CreateJWT(strconv.Itoa(params.UserID), 0) if err != nil { - log.Printf("JWT creation failed: %v", err) + h.Logger.Error("JWT creation failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "") return } @@ -512,14 +516,14 @@ func (h *Handler) InterviewsHandler(w http.ResponseWriter, r *http.Request) { params := &middleware.AcceptedVals{} err := json.NewDecoder(r.Body).Decode(params) if err != nil { - log.Printf("Decoding params failed: %v", err) + h.Logger.Error("Decoding params failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Internal Server Error") return } userReturned, err := user.GetUser(h.UserRepo, userID) if err != nil { - log.Printf("GetUser error: %v", err) + h.Logger.Error("GetUser error", "error", err) RespondWithError(w, http.StatusInternalServerError, "Failed to find user") return } @@ -537,7 +541,7 @@ func (h *Handler) InterviewsHandler(w http.ResponseWriter, r *http.Request) { if err != nil { var openaiErr *chatgpt.OpenAIError if errors.As(err, &openaiErr) { - log.Printf("OpenAI error: %v", openaiErr) + h.Logger.Error("OpenAI error", "error", openaiErr) RespondWithError(w, openaiErr.StatusCode, openaiErr.Message) return } @@ -545,21 +549,21 @@ func (h *Handler) InterviewsHandler(w http.ResponseWriter, r *http.Request) { RespondWithError(w, http.StatusPaymentRequired, "You do not have enough credits to start a new interview or your subscription has expired.") return } - log.Printf("Interview failed to start: %v", err) + h.Logger.Error("Interview failed to start", "error", err) RespondWithError(w, http.StatusInternalServerError, "Failed to start interview.") return } conversationID, err := conversation.CreateEmptyConversation(h.ConversationRepo, interviewStarted.Id, interviewStarted.Subtopic) if err != nil { - log.Printf("conversation.CreateEmptyConversation failed: %v", err) + h.Logger.Error("conversation.CreateEmptyConversation failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } err = interview.LinkConversation(h.InterviewRepo, interviewStarted.Id, conversationID) if err != nil { - log.Printf("interview.LinkConversation failed: %v", err) + h.Logger.Error("interview.LinkConversation failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } @@ -587,19 +591,19 @@ func (h *Handler) GetInterviewHandler(w http.ResponseWriter, r *http.Request) { interviewID, err := GetPathID(r, "/api/interviews/") if err != nil { - log.Printf("GetPathID failed: %v", err) + h.Logger.Error("GetPathID failed", "error", err) RespondWithError(w, http.StatusBadRequest, "Invalid interview ID") return } interviewReturned, err := interview.GetInterview(h.InterviewRepo, interviewID) if err != nil { - log.Printf("GetInterview failed: %v", err) + h.Logger.Error("GetInterview failed", "error", err) RespondWithError(w, http.StatusNotFound, "Interview not found") return } if interviewReturned.UserId != userID { - log.Printf("User ID mismatch on interview fetch") + h.Logger.Error("User ID mismatch on interview fetch", "got", userID, "want", interviewReturned.UserId) RespondWithError(w, http.StatusUnauthorized, "Unauthorized") return } @@ -639,21 +643,21 @@ func (h *Handler) UpdateInterviewStatusHandler(w http.ResponseWriter, r *http.Re interviewReturned, err := interview.GetInterview(h.InterviewRepo, interviewID) if err != nil { - log.Printf("GetInterview error: %v\n", err) + h.Logger.Error("GetInterview error", "error", err) RespondWithError(w, http.StatusBadRequest, "Invalid ID") return } err = ValidateInterviewStatusTransition(interviewReturned.Status, payload.Status) if err != nil { - log.Printf("ValidateInterviewStatusTransition failed: %v", err) + h.Logger.Error("ValidateInterviewStatusTransition failed", "error", err) RespondWithError(w, http.StatusBadRequest, "Invalid status transition") return } err = h.InterviewRepo.UpdateStatus(interviewID, userID, payload.Status) if err != nil { - log.Printf("UpdateInterviewStatus failed: %v", err) + h.Logger.Error("UpdateInterviewStatus failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Could not update status") return } @@ -676,7 +680,7 @@ func (h *Handler) CreateConversationsHandler(w http.ResponseWriter, r *http.Requ params := &middleware.AcceptedVals{} err := json.NewDecoder(r.Body).Decode(params) if err != nil { - log.Printf("Decoding params failed: %v", err) + h.Logger.Error("Decoding params failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Internal Server Error") return } @@ -684,19 +688,19 @@ func (h *Handler) CreateConversationsHandler(w http.ResponseWriter, r *http.Requ interviewID, err := GetPathID(r, "/api/conversations/create/") if err != nil { - log.Printf("PathID error: %v\n", err) + h.Logger.Error("PathID error", "error", err) RespondWithError(w, http.StatusBadRequest, "Missing ID") return } interviewReturned, err := interview.GetInterview(h.InterviewRepo, interviewID) if err != nil { - log.Printf("GetInterview error: %v\n", err) + h.Logger.Error("GetInterview error", "error", err) RespondWithError(w, http.StatusBadRequest, "Invalid ID") return } if interviewReturned.UserId != userID { - log.Printf("interview.userid != token user_id") + h.Logger.Error("userID does not exist", "got", userID, "want", interviewReturned.UserId) RespondWithError(w, http.StatusUnauthorized, "Unauthorized") return } @@ -707,7 +711,7 @@ func (h *Handler) CreateConversationsHandler(w http.ResponseWriter, r *http.Requ conversationReturned, err := conversation.GetConversation(h.ConversationRepo, interviewID) if err != nil { - log.Printf("conversation.GetConversation failed: %v", err) + h.Logger.Error("conversation.GetConversation failed", "error", err) RespondWithError(w, http.StatusBadRequest, "Invalid ID") return } @@ -725,11 +729,11 @@ func (h *Handler) CreateConversationsHandler(w http.ResponseWriter, r *http.Requ if err != nil { var openaiErr *chatgpt.OpenAIError if errors.As(err, &openaiErr) { - log.Printf("OpenAI error: %v", openaiErr) + h.Logger.Error("OpenAI error", "error", openaiErr) RespondWithError(w, openaiErr.StatusCode, openaiErr.Message) return } - log.Printf("CreateConversation error: %v", err) + h.Logger.Error("CreateConversation error", "error", err) RespondWithError(w, http.StatusBadRequest, "Invalid interview_id") return } @@ -755,32 +759,32 @@ func (h *Handler) AppendConversationsHandler(w http.ResponseWriter, r *http.Requ params := &middleware.AcceptedVals{} err := json.NewDecoder(r.Body).Decode(params) if err != nil { - log.Printf("Decoding params failed: %v", err) + h.Logger.Error("Decoding params failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Internal Server Error") return } if params.Message == "" { - log.Printf("messageUserResponse is nil") + h.Logger.Error("messageUserResponse is nil") RespondWithError(w, http.StatusBadRequest, "Missing message") return } interviewID, err := GetPathID(r, "/api/conversations/append/") if err != nil { - log.Printf("PathID error: %v\n", err) + h.Logger.Error("PathID error", "error", err) RespondWithError(w, http.StatusBadRequest, "Missing ID") return } interviewReturned, err := interview.GetInterview(h.InterviewRepo, interviewID) if err != nil { - log.Printf("GetInterview error: %v\n", err) + h.Logger.Error("GetInterview error", "error", err) RespondWithError(w, http.StatusBadRequest, "Invalid ID") return } if interviewReturned.UserId != userID { - log.Printf("interview.userid != token user_id") + h.Logger.Error("incorrect userID", "got", userID, "want", interviewReturned.UserId) RespondWithError(w, http.StatusUnauthorized, "Unauthorized") return } @@ -791,7 +795,7 @@ func (h *Handler) AppendConversationsHandler(w http.ResponseWriter, r *http.Requ conversationReturned, err := conversation.GetConversation(h.ConversationRepo, interviewID) if err != nil { - log.Printf("GetConversation error: %v", err) + h.Logger.Error("GetConversation error", "error", err) RespondWithError(w, http.StatusBadRequest, "Invalid ID.") return } @@ -808,11 +812,11 @@ func (h *Handler) AppendConversationsHandler(w http.ResponseWriter, r *http.Requ if err != nil { var openaiErr *chatgpt.OpenAIError if errors.As(err, &openaiErr) { - log.Printf("OpenAI error: %v", openaiErr) + h.Logger.Error("OpenAI error", "error", openaiErr) RespondWithError(w, openaiErr.StatusCode, openaiErr.Message) return } - log.Printf("AppendConversation error: %v", err) + h.Logger.Error("AppendConversation error", "error", err) RespondWithError(w, http.StatusBadRequest, "Invalid ID.") return } @@ -837,26 +841,26 @@ func (h *Handler) GetConversationHandler(w http.ResponseWriter, r *http.Request) interviewID, err := GetPathID(r, "/api/conversations/") if err != nil { - log.Printf("PathID error: %v\n", err) + h.Logger.Error("PathID error", "error", err) RespondWithError(w, http.StatusBadRequest, "Missing ID") return } interviewReturned, err := interview.GetInterview(h.InterviewRepo, interviewID) if err != nil { - log.Printf("GetInterview error: %v\n", err) + h.Logger.Error("GetInterview error", "error", err) RespondWithError(w, http.StatusBadRequest, "Invalid ID") return } if interviewReturned.UserId != userID { - log.Printf("interview.userid != token user_id") + h.Logger.Error("incorrect userID", "got", userID, "want", interviewReturned.UserId) RespondWithError(w, http.StatusUnauthorized, "Unauthorized") return } conversationReturned, err := conversation.GetConversation(h.ConversationRepo, interviewID) if err != nil { - log.Printf("GetConversation error: %v", err) + h.Logger.Error("GetConversation error", "error", err) RespondWithError(w, http.StatusBadRequest, "Invalid ID.") return } @@ -876,14 +880,14 @@ func (h *Handler) RequestResetHandler(w http.ResponseWriter, r *http.Request) { var params PasswordResetRequest err := json.NewDecoder(r.Body).Decode(¶ms) if err != nil { - log.Printf("Decoding request failed: %v", err) + h.Logger.Error("Decoding request failed", "error", err) RespondWithError(w, http.StatusBadRequest, "Invalid request body") return } resetJWT, err := user.RequestPasswordReset(h.UserRepo, params.Email) if err != nil { - log.Printf("Error generating reset token for email %s: %v", params.Email, err) + h.Logger.Error("Error generating reset token for email", "error", err) w.WriteHeader(http.StatusOK) return } @@ -894,7 +898,7 @@ func (h *Handler) RequestResetHandler(w http.ResponseWriter, r *http.Request) { go func(email, resetURL string) { err := h.Mailer.SendPasswordReset(email, resetURL) if err != nil { - log.Printf("SendPasswordReset error: %v", err) + h.Logger.Error("SendPasswordReset error", "error", err) return } }(params.Email, resetURL) @@ -911,13 +915,13 @@ func (h *Handler) ResetPasswordHandler(w http.ResponseWriter, r *http.Request) { var params PasswordResetPayload if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil { - log.Printf("Decoding payload failed: %v", err) + h.Logger.Error("Decoding payload failed", "error", err) RespondWithError(w, http.StatusBadRequest, "Invalid request body") return } err := user.ResetPassword(h.UserRepo, params.NewPassword, params.Token) if err != nil { - log.Printf("ResetPasswordHandler failed: %v", err) + h.Logger.Error("ResetPasswordHandler failed", "error", err) RespondWithError(w, http.StatusUnauthorized, "Invalid or expired token") return } @@ -934,14 +938,14 @@ func (h *Handler) CreateCheckoutSessionHandler(w http.ResponseWriter, r *http.Re userID, ok := r.Context().Value(middleware.ContextKeyTokenParams).(int) if !ok { - log.Printf("r.Context().Value() failed") + h.Logger.Error("r.Context().Value() error") RespondWithError(w, http.StatusUnauthorized, "Unauthorized") return } var params CheckoutRequest if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil || params.Tier == "" { - log.Printf("jsonNewDecoder failed: %v", err) + h.Logger.Error("jsonNewDecoder failed", "error", err) RespondWithError(w, http.StatusBadRequest, "Missing or invalid tier") return } @@ -967,14 +971,14 @@ func (h *Handler) CreateCheckoutSessionHandler(w http.ResponseWriter, r *http.Re priceIDInt, err := strconv.Atoi(priceID) if err != nil { - log.Printf("strconv.Atoi() failed: %v", err) + h.Logger.Error("strconv.Atoi() failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } url, err := h.Billing.RequestCheckoutSession(user.Email, priceIDInt) if err != nil { - log.Printf("billing.CreateCheckoutSession failed: %v", err) + h.Logger.Error("billing.CreateCheckoutSession failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Could not start checkout") return } @@ -996,14 +1000,14 @@ func (h *Handler) CancelSubscriptionHandler(w http.ResponseWriter, r *http.Reque userReturned, err := user.GetUser(h.UserRepo, userID) if err != nil { - log.Printf("GetUser failed: %v", err) + h.Logger.Error("GetUser failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Could not retrieve user") return } err = h.Billing.RequestDeleteSubscription(userReturned.SubscriptionID) if err != nil { - log.Printf("DeleteSubscription failed: %v", err) + h.Logger.Error("DeleteSubscription failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Could not cancel subscription") return } @@ -1025,14 +1029,14 @@ func (h *Handler) ResumeSubscriptionHandler(w http.ResponseWriter, r *http.Reque userReturned, err := user.GetUser(h.UserRepo, userID) if err != nil { - log.Printf("GetUser failed: %v", err) + h.Logger.Error("GetUser failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Could not retrieve user") return } err = h.Billing.RequestResumeSubscription(userReturned.SubscriptionID) if err != nil { - log.Printf("DeleteSubscription failed: %v", err) + h.Logger.Error("DeleteSubscription failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Could not cancel subscription") return } @@ -1054,7 +1058,7 @@ func (h *Handler) ChangePlanHandler(w http.ResponseWriter, r *http.Request) { var params CheckoutRequest if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil || params.Tier == "" { - log.Printf("jsonNewDecoder failed: %v", err) + h.Logger.Error("jsonNewDecoder failed", "error", err) RespondWithError(w, http.StatusBadRequest, "Missing or invalid tier") return } @@ -1078,13 +1082,13 @@ func (h *Handler) ChangePlanHandler(w http.ResponseWriter, r *http.Request) { priceIDInt, err := strconv.Atoi(priceID) if err != nil { - log.Printf("strconv.Atoi() failed: %v", err) + h.Logger.Error("strconv.Atoi() failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } if err := h.Billing.RequestUpdateSubscriptionVariant(user.SubscriptionID, priceIDInt); err != nil { - log.Printf("UpdateLemonSubscriptionVariant failed: %v", err) + h.Logger.Error("UpdateLemonSubscriptionVariant failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update subscription") return } @@ -1100,7 +1104,7 @@ func (h *Handler) BillingWebhookHandler(w http.ResponseWriter, r *http.Request) body, err := io.ReadAll(r.Body) if err != nil { - log.Printf("io.ReadAll failed: %v", err) + h.Logger.Error("io.ReadAll failed", "error", err) RespondWithError(w, http.StatusBadRequest, "Bad Request") return } @@ -1108,7 +1112,7 @@ func (h *Handler) BillingWebhookHandler(w http.ResponseWriter, r *http.Request) signature := r.Header.Get("X-Signature") if !h.Billing.VerifyBillingSignature(signature, body, os.Getenv("LEMON_WEBHOOK_SECRET")) { - log.Printf("Invalid billing event signature") + h.Logger.Error("Invalid billing event signature") RespondWithError(w, http.StatusUnauthorized, "Invalid signature") return } @@ -1116,7 +1120,7 @@ func (h *Handler) BillingWebhookHandler(w http.ResponseWriter, r *http.Request) var webhookPayload billing.BillingWebhookPayload err = json.Unmarshal(body, &webhookPayload) if err != nil { - log.Printf("json.Unmarshal failed: %v", err) + h.Logger.Error("json.Unmarshal failed", "error", err) RespondWithError(w, http.StatusBadRequest, "Invalid JSON") return } @@ -1124,12 +1128,12 @@ func (h *Handler) BillingWebhookHandler(w http.ResponseWriter, r *http.Request) webhookID := webhookPayload.Meta.WebhookID exists, err := h.BillingRepo.HasWebhookBeenProcessed(webhookID) if err != nil { - log.Printf("h.BillingRepo.HasWebhookBeenProcessed failed: %v", err) + h.Logger.Error("h.BillingRepo.HasWebhookBeenProcessed failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Error checking webhook") return } if exists { - log.Printf("Webhook %s already processed", webhookID) + h.Logger.Info("Webhook already processed", "webhookID", webhookID) w.WriteHeader(http.StatusOK) return } @@ -1140,170 +1144,170 @@ func (h *Handler) BillingWebhookHandler(w http.ResponseWriter, r *http.Request) eventType := webhookPayload.Meta.EventName - log.Printf("Received webhook: eventType=%q, webhookID=%s, subscriptionID=%s", eventType, webhookID, subscriptionID) + h.Logger.Info("Received webhook", "eventType", eventType, "webhookID", webhookID, "subscriptionID", subscriptionID) switch eventType { case "order_created": var orderAttrs billing.OrderAttributes if err := json.Unmarshal(webhookPayload.Data.Attributes, &orderAttrs); err != nil { - log.Printf("Unmarshal order_created failed: %v", err) + h.Logger.Error("Unmarshal order_created failed", "error", err) RespondWithError(w, http.StatusBadRequest, "Invalid order_created payload") return } err = h.Billing.ApplyCredits(h.UserRepo, h.BillingRepo, orderAttrs.UserEmail, orderAttrs.FirstOrderItem.VariantID) if err != nil { - log.Printf("h.Billing.ApplyCredits failed: %v", err) + h.Logger.Error("h.Billing.ApplyCredits failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } case "subscription_created": var SubCreatedAttrs billing.SubscriptionAttributes if err := json.Unmarshal(webhookPayload.Data.Attributes, &SubCreatedAttrs); err != nil { - log.Printf("Unmarshal subscription_created failed: %v", err) + h.Logger.Error("Unmarshal subscription_created failed", "error", err) RespondWithError(w, http.StatusBadRequest, "Invalid subscription_created payload") return } exists, err := h.UserRepo.HasActiveOrCancelledSubscription(SubCreatedAttrs.UserEmail) if err != nil { - log.Printf("Subscription duplicate check failed: %v", err) + h.Logger.Error("Subscription duplicate check failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Subscription check failed") return } if exists { - log.Printf("Duplicate subscription attempt blocked for %s", SubCreatedAttrs.UserEmail) + h.Logger.Info("Duplicate subscription attempt blocked", "userEmail", SubCreatedAttrs.UserEmail) return } err = h.Billing.CreateSubscription(h.UserRepo, SubCreatedAttrs, subscriptionID) if err != nil { - log.Printf("h.Billing.CreateSubscription failed: %v", err) + h.Logger.Error("h.Billing.CreateSubscription failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } case "subscription_cancelled": if err := json.Unmarshal(webhookPayload.Data.Attributes, &emailAttribute); err != nil { - log.Printf("Unmarshal subscription_cancelled failed: %v", err) + h.Logger.Error("Unmarshal subscription_cancelled failed", "error", err) RespondWithError(w, http.StatusBadRequest, "Invalid subscription_cancelled payload") return } err = h.Billing.CancelSubscription(h.UserRepo, emailAttribute.UserEmail) if err != nil { - log.Printf("h.Billing.CancelSubscription failed: %v", err) + h.Logger.Error("h.Billing.CancelSubscription failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } case "subscription_resumed": if err := json.Unmarshal(webhookPayload.Data.Attributes, &emailAttribute); err != nil { - log.Printf("Unmarshal subscription_resumed failed: %v", err) + h.Logger.Error("Unmarshal subscription_resumed failed", "error", err) RespondWithError(w, http.StatusBadRequest, "Invalid subscription_resumed payload") return } err = h.Billing.ResumeSubscription(h.UserRepo, emailAttribute.UserEmail) if err != nil { - log.Printf("h.Billing.ResumeSubscription failed: %v", err) + h.Logger.Error("h.Billing.ResumeSubscription failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } case "subscription_expired": if err := json.Unmarshal(webhookPayload.Data.Attributes, &emailAttribute); err != nil { - log.Printf("Unmarshal subscription_expired failed: %v", err) + h.Logger.Error("Unmarshal subscription_expired failed", "error", err) RespondWithError(w, http.StatusBadRequest, "Invalid subscription_expired payload") return } err = h.Billing.ExpireSubscription(h.UserRepo, h.BillingRepo, emailAttribute.UserEmail) if err != nil { - log.Printf("h.Billing.ExpireSubscription failed: %v", err) + h.Logger.Error("h.Billing.ExpireSubscription failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } case "subscription_payment_success": var SubRenewAttrs billing.SubscriptionRenewAttributes if err := json.Unmarshal(webhookPayload.Data.Attributes, &SubRenewAttrs); err != nil { - log.Printf("Unmarshal subscription_payment_success failed: %v", err) + h.Logger.Error("Unmarshal subscription_payment_success failed", "error", err) RespondWithError(w, http.StatusBadRequest, "Invalid subscription_payment_success payload") return } if SubRenewAttrs.BillingReason == "initial" { - log.Println("Skipping credits on initial charge (already granted via order_created)") + h.Logger.Info("Skipping credits on initial charge (already granted via order_created)") return } err = h.Billing.RenewSubscription(h.UserRepo, h.BillingRepo, SubRenewAttrs) if err != nil { - log.Printf("h.Billing.RenewSubscription failed: %v", err) + h.Logger.Error("h.Billing.RenewSubscription failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } case "subscription_plan_changed": var SubChangedAttrs billing.SubscriptionAttributes if err := json.Unmarshal(webhookPayload.Data.Attributes, &SubChangedAttrs); err != nil { - log.Printf("Unmarshal subscription_plan_changed failed: %v", err) + h.Logger.Error("Unmarshal subscription_plan_changed failed", "error", err) RespondWithError(w, http.StatusBadRequest, "Invalid subscription_plan_changed payload") return } err = h.Billing.ChangeSubscription(h.UserRepo, h.BillingRepo, SubChangedAttrs) if err != nil { - log.Printf("h.Billing.ChangeSubscription failed: %v", err) + h.Logger.Error("h.Billing.ChangeSubscription failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } case "subscription_updated": var SubChangedAttrs billing.SubscriptionAttributes if err := json.Unmarshal(webhookPayload.Data.Attributes, &SubChangedAttrs); err != nil { - log.Printf("Unmarshal subscription_updated failed: %v", err) + h.Logger.Error("Unmarshal subscription_updated failed", "error", err) RespondWithError(w, http.StatusBadRequest, "Invalid subscription_updated payload") return } err = h.Billing.UpdateSubscription(h.UserRepo, SubChangedAttrs, subscriptionID) if err != nil { - log.Printf("h.Billing.UpdateSubscription failed: %v", err) + h.Logger.Error("h.Billing.UpdateSubscription failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } case "order_refunded": var orderAttrs billing.OrderAttributes if err := json.Unmarshal(webhookPayload.Data.Attributes, &orderAttrs); err != nil { - log.Printf("Unmarshal order_created failed: %v", err) + h.Logger.Error("Unmarshal order_created failed", "error", err) RespondWithError(w, http.StatusBadRequest, "Invalid order_created payload") return } err = h.Billing.DeductCredits(h.UserRepo, h.BillingRepo, orderAttrs) if err != nil { - log.Printf("h.Billing.DeductCredits failed: %v", err) + h.Logger.Error("h.Billing.DeductCredits failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } case "subscription_payment_failed", "subscription_payment_recovered": if err := json.Unmarshal(webhookPayload.Data.Attributes, &emailAttribute); err != nil { - log.Printf("Unmarshal %s failed: %v", eventType, err) + h.Logger.Error("Unmarshal failed", "eventType", eventType, "error", err) RespondWithError(w, http.StatusBadRequest, "Invalid payment status payload") return } - log.Printf("Payment event: %s for user %s", eventType, emailAttribute.UserEmail) + h.Logger.Info("Payment event", "eventType", eventType, "user", emailAttribute.UserEmail) default: - log.Printf("Unhandled event type: %s", eventType) + h.Logger.Info("Unhandled event type", "eventType", eventType) RespondWithError(w, http.StatusNotImplemented, "Unhandled event type") return } if err != nil { - log.Printf("eventType switch func failed: %v", err) + h.Logger.Error("eventType switch func failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Failed to update user") return } err = h.BillingRepo.MarkWebhookProcessed(webhookID, eventType) if err != nil { - log.Printf("MarkWebhookProcessed failed: %v", err) + h.Logger.Error("MarkWebhookProcessed failed", "error", err) w.WriteHeader(http.StatusOK) return } @@ -1329,7 +1333,7 @@ func (h *Handler) DashboardHandler(w http.ResponseWriter, r *http.Request) { RespondWithError(w, http.StatusUnauthorized, "User not found") return } - log.Printf("dashboard.GetDashboardData failed: %v", err) + h.Logger.Error("dashboard.GetDashboardData failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Could not load dashboard") return } @@ -1355,11 +1359,11 @@ func (h *Handler) JDInputHandler(w http.ResponseWriter, r *http.Request) { if err != nil { var openaiErr *chatgpt.OpenAIError if errors.As(err, &openaiErr) { - log.Printf("OpenAI error: %v", openaiErr) + h.Logger.Error("OpenAI error", "error", openaiErr) RespondWithError(w, openaiErr.StatusCode, openaiErr.Message) return } - log.Printf("chatgpt.ExtractJDInput failed: %v", err) + h.Logger.Error("chatgpt.ExtractJDInput failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Failed to process job description") return } @@ -1368,11 +1372,11 @@ func (h *Handler) JDInputHandler(w http.ResponseWriter, r *http.Request) { if err != nil { var openaiErr *chatgpt.OpenAIError if errors.As(err, &openaiErr) { - log.Printf("OpenAI error: %v", openaiErr) + h.Logger.Error("OpenAI error", "error", openaiErr) RespondWithError(w, openaiErr.StatusCode, openaiErr.Message) return } - log.Printf("chatgpt.ExtractJDSummary failed: %v", err) + h.Logger.Error("chatgpt.ExtractJDSummary failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Failed to process job description") return diff --git a/handlers/helpers.go b/handlers/helpers.go index 052145b..9bceae4 100644 --- a/handlers/helpers.go +++ b/handlers/helpers.go @@ -37,7 +37,7 @@ func GetPathID(r *http.Request, prefix string) (int, error) { if path == "" { log.Printf("getPathID returned empty string") - err := errors.New("Missing or invalid url param") + err := errors.New("missing or invalid url param") return 0, err } From 28a5405d78c8aff2234f49d68f226837ee7bda62 Mon Sep 17 00:00:00 2001 From: MichaelBoegner Date: Sat, 20 Sep 2025 12:26:11 +0700 Subject: [PATCH 04/15] passed slog to chatgpt package --- chatgpt/model.go | 5 ++++- chatgpt/service.go | 19 +++++++++---------- internal/server/server.go | 2 +- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/chatgpt/model.go b/chatgpt/model.go index c143ab2..9a1c81b 100644 --- a/chatgpt/model.go +++ b/chatgpt/model.go @@ -2,6 +2,7 @@ package chatgpt import ( "fmt" + "log/slog" "os" "strings" ) @@ -24,6 +25,7 @@ type ChatGPTResponse struct { type OpenAIClient struct { APIKey string + Logger *slog.Logger } type OpenAIError struct { @@ -43,9 +45,10 @@ func (e *OpenAIError) Error() string { return fmt.Sprintf("OpenAI error %d: %s", e.StatusCode, e.Message) } -func NewOpenAI() *OpenAIClient { +func NewOpenAI(logger *slog.Logger) *OpenAIClient { return &OpenAIClient{ APIKey: os.Getenv("OPENAI_API_KEY"), + Logger: logger, } } diff --git a/chatgpt/service.go b/chatgpt/service.go index e627646..03c6d19 100644 --- a/chatgpt/service.go +++ b/chatgpt/service.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "io" - "log" "net/http" "strings" ) @@ -33,7 +32,7 @@ func (c *OpenAIClient) GetChatGPTResponse(prompt string) (*ChatGPTResponse, erro req, err := http.NewRequestWithContext(ctx, "POST", "https://api.openai.com/v1/chat/completions", bytes.NewBuffer(requestBody)) if err != nil { - log.Printf("NewRequestWithContext failed: %v", err) + c.Logger.Error("NewRequestWithContext failed", "error", err) return nil, err } @@ -64,7 +63,7 @@ func (c *OpenAIClient) GetChatGPTResponse(prompt string) (*ChatGPTResponse, erro var result map[string]interface{} if err := json.Unmarshal(body, &result); err != nil { - log.Printf("Unmarshal result err: %v", err) + c.Logger.Error("Unmarshal result err", "error", err) return nil, err } @@ -78,7 +77,7 @@ func (c *OpenAIClient) GetChatGPTResponse(prompt string) (*ChatGPTResponse, erro var chatGPTResponse ChatGPTResponse if err := json.Unmarshal([]byte(chatGPTResponseRaw), &chatGPTResponse); err != nil { - log.Printf("Unmarshal chatGPTResponse err: %v", err) + c.Logger.Error("Unmarshal chatGPTResponse err", "error", err) return nil, err } @@ -100,7 +99,7 @@ func (c *OpenAIClient) GetChatGPTResponseConversation(conversationHistory []map[ req, err := http.NewRequestWithContext(ctx, "POST", "https://api.openai.com/v1/chat/completions", bytes.NewBuffer(requestBody)) if err != nil { - log.Printf("NewRequestWithContext failed: %v", err) + c.Logger.Error("NewRequestWithContext failed", "error", err) return nil, err } @@ -131,7 +130,7 @@ func (c *OpenAIClient) GetChatGPTResponseConversation(conversationHistory []map[ var result map[string]interface{} if err := json.Unmarshal(body, &result); err != nil { - log.Printf("Unmarshal result err: %v", err) + c.Logger.Error("Unmarshal result err", "error", err) return nil, err } @@ -145,7 +144,7 @@ func (c *OpenAIClient) GetChatGPTResponseConversation(conversationHistory []map[ var chatGPTResponse ChatGPTResponse if err := json.Unmarshal([]byte(chatGPTResponseRaw), &chatGPTResponse); err != nil { - log.Printf("Unmarshal chatGPTResponse err: %v", err) + c.Logger.Error("Unmarshal chatGPTResponse err", "error", err) return nil, err } @@ -173,7 +172,7 @@ func (c *OpenAIClient) GetChatGPT35Response(prompt string) (*ChatGPTResponse, er req, err := http.NewRequestWithContext(ctx, "POST", "https://api.openai.com/v1/chat/completions", bytes.NewBuffer(requestBody)) if err != nil { - log.Printf("NewRequestWithContext failed: %v", err) + c.Logger.Error("NewRequestWithContext failed", "error", err) return nil, err } @@ -204,7 +203,7 @@ func (c *OpenAIClient) GetChatGPT35Response(prompt string) (*ChatGPTResponse, er var result map[string]interface{} if err := json.Unmarshal(body, &result); err != nil { - log.Printf("Unmarshal result err: %v", err) + c.Logger.Error("Unmarshal result err", "error", err) return nil, err } @@ -218,7 +217,7 @@ func (c *OpenAIClient) GetChatGPT35Response(prompt string) (*ChatGPTResponse, er var chatGPTResponse ChatGPTResponse if err := json.Unmarshal([]byte(chatGPTResponseRaw), &chatGPTResponse); err != nil { - log.Printf("Unmarshal chatGPTResponse err: %v", err) + c.Logger.Error("Unmarshal chatGPTResponse err", "error", err) return nil, err } diff --git a/internal/server/server.go b/internal/server/server.go index 59e033b..cae0c30 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -35,7 +35,7 @@ func NewServer(logger *slog.Logger) (*Server, error) { tokenRepo := token.NewRepository(db) conversationRepo := conversation.NewRepository(db) billingRepo := billing.NewRepository(db) - openAI := chatgpt.NewOpenAI() + openAI := chatgpt.NewOpenAI(logger) mailer := mailer.NewMailer() billing, err := billing.NewBilling() if err != nil { From 29f2abb56e9db9474936f3b7ead659e35a00e079 Mon Sep 17 00:00:00 2001 From: MichaelBoegner Date: Sat, 20 Sep 2025 12:46:38 +0700 Subject: [PATCH 05/15] added slog to Billing package --- billing/model.go | 5 ++- billing/service.go | 71 +++++++++++++++++++-------------------- internal/server/server.go | 4 +-- mailer/model.go | 9 +++-- mailer/service.go | 17 +++++----- 5 files changed, 56 insertions(+), 50 deletions(-) diff --git a/billing/model.go b/billing/model.go index c74554c..10f6bf0 100644 --- a/billing/model.go +++ b/billing/model.go @@ -3,6 +3,7 @@ package billing import ( "encoding/json" "fmt" + "log/slog" "os" "strconv" "time" @@ -13,6 +14,7 @@ type Billing struct { VariantIDIndividual int VariantIDPro int VariantIDPremium int + Logger *slog.Logger } type CheckoutPayload struct { Data CheckoutData `json:"data"` @@ -100,7 +102,7 @@ type BillingRepo interface { MarkWebhookProcessed(id string, event string) error } -func NewBilling() (*Billing, error) { +func NewBilling(logger *slog.Logger) (*Billing, error) { individualID, err := strconv.Atoi(os.Getenv("LEMON_VARIANT_ID_INDIVIDUAL")) if err != nil { return nil, fmt.Errorf("invalid INDIVIDUAL ID: %w", err) @@ -118,5 +120,6 @@ func NewBilling() (*Billing, error) { VariantIDIndividual: individualID, VariantIDPro: proID, VariantIDPremium: premiumID, + Logger: logger, }, nil } diff --git a/billing/service.go b/billing/service.go index dcd0607..fb3fd41 100644 --- a/billing/service.go +++ b/billing/service.go @@ -8,7 +8,6 @@ import ( "encoding/json" "fmt" "io" - "log" "net/http" "os" "strconv" @@ -45,13 +44,13 @@ func (b *Billing) RequestCheckoutSession(userEmail string, variantID int) (strin body, err := json.Marshal(payload) if err != nil { - log.Printf("json.Marshal failed: %v", err) + b.Logger.Error("json.Marshal failed", "error", err) return "", err } req, err := http.NewRequest("POST", "https://api.lemonsqueezy.com/v1/checkouts", bytes.NewBuffer(body)) if err != nil { - log.Printf("http.NewRequest failed: %v", err) + b.Logger.Error("http.NewRequest failed", "error", err) return "", err } @@ -64,20 +63,20 @@ func (b *Billing) RequestCheckoutSession(userEmail string, variantID int) (strin } res, err := client.Do(req) if err != nil { - log.Printf("client.Do failed: %v", err) + b.Logger.Error("client.Do failed", "error", err) return "", err } defer res.Body.Close() if res.StatusCode < 200 || res.StatusCode >= 300 { bodyBytes, _ := io.ReadAll(res.Body) - log.Printf("LemonSqueezy returned error: %s", string(bodyBytes)) + b.Logger.Error("LemonSqueezy returned error", "error", string(bodyBytes)) return "", fmt.Errorf("LemonSqueezy API error: %s", res.Status) } var result CheckoutResponse if err := json.NewDecoder(res.Body).Decode(&result); err != nil { - log.Printf("NewDecoder failed: %v", err) + b.Logger.Error("NewDecoder failed", "error", err) return "", err } @@ -190,7 +189,7 @@ func (b *Billing) VerifyBillingSignature(signature string, body []byte, secret s func (b *Billing) ApplyCredits(userRepo user.UserRepo, billingRepo BillingRepo, email string, variantID int) error { user, err := userRepo.GetUserByEmail(email) if err != nil { - log.Printf("repo.GetUserByEmail failed: %v", err) + b.Logger.Error("repo.GetUserByEmail failed", "error", err) return err } @@ -213,12 +212,12 @@ func (b *Billing) ApplyCredits(userRepo user.UserRepo, billingRepo BillingRepo, creditType = "subscription" reason = "Premium subscription monthly credit grant" default: - log.Printf("ERROR: unknown variantID: %d", variantID) + b.Logger.Error("ERROR: unknown variantID", "error", variantID) return fmt.Errorf("unknown variant ID: %d", variantID) } if err := userRepo.AddCredits(user.ID, credits, creditType); err != nil { - log.Printf("repo.AddCredits failed: %v", err) + b.Logger.Error("repo.AddCredits failed", "error", err) return err } @@ -229,7 +228,7 @@ func (b *Billing) ApplyCredits(userRepo user.UserRepo, billingRepo BillingRepo, Reason: reason, } if err := billingRepo.LogCreditTransaction(tx); err != nil { - log.Printf("Warning: credit granted but failed to log transaction: %v", err) + b.Logger.Error("Warning: credit granted but failed to log transaction", "error", err) return err } @@ -239,7 +238,7 @@ func (b *Billing) ApplyCredits(userRepo user.UserRepo, billingRepo BillingRepo, func (b *Billing) DeductCredits(userRepo user.UserRepo, billingRepo BillingRepo, orderAttrs OrderAttributes) error { user, err := userRepo.GetUserByEmail(orderAttrs.UserEmail) if err != nil { - log.Printf("repo.GetUserByEmail failed: %v", err) + b.Logger.Error("repo.GetUserByEmail failed", "error", err) return err } @@ -264,12 +263,12 @@ func (b *Billing) DeductCredits(userRepo user.UserRepo, billingRepo BillingRepo, creditType = "subscription" reason = "Premium subscription monthly credit refund" default: - log.Printf("ERROR: unknown variantID: %d", variantID) + b.Logger.Error("unknown variantID", "variantID", variantID) return fmt.Errorf("unknown variant ID: %d", variantID) } if err := userRepo.AddCredits(user.ID, -credits, creditType); err != nil { - log.Printf("repo.DeductCredits failed: %v", err) + b.Logger.Error("repo.DeductCredits failed", "error", err) return err } @@ -280,7 +279,7 @@ func (b *Billing) DeductCredits(userRepo user.UserRepo, billingRepo BillingRepo, Reason: reason, } if err := billingRepo.LogCreditTransaction(tx); err != nil { - log.Printf("Warning: refund deduction succeeded but failed to log transaction: %v", err) + b.Logger.Warn("Refund deduction succeeded but failed to log transaction", "error", err) return err } @@ -290,7 +289,7 @@ func (b *Billing) DeductCredits(userRepo user.UserRepo, billingRepo BillingRepo, func (b *Billing) CreateSubscription(userRepo user.UserRepo, subCreatedAttrs SubscriptionAttributes, subscriptionID string) error { user, err := userRepo.GetUserByEmail(subCreatedAttrs.UserEmail) if err != nil { - log.Printf("repo.GetUserByEmail failed: %v", err) + b.Logger.Error("repo.GetUserByEmail failed", "error", err) return err } @@ -301,7 +300,7 @@ func (b *Billing) CreateSubscription(userRepo user.UserRepo, subCreatedAttrs Sub case b.VariantIDPremium: tier = "premium" default: - log.Printf("ERROR: unknown variantID: %d", subCreatedAttrs.VariantID) + b.Logger.Error("unknown variantID", "variantID", subCreatedAttrs.VariantID) return fmt.Errorf("unknown variant ID: %d", subCreatedAttrs.VariantID) } @@ -314,7 +313,7 @@ func (b *Billing) CreateSubscription(userRepo user.UserRepo, subCreatedAttrs Sub subCreatedAttrs.EndsAt, ) if err != nil { - log.Printf("CreateSubscriptionData failed: %v", err) + b.Logger.Error("CreateSubscriptionData failed", "error", err) return err } @@ -324,7 +323,7 @@ func (b *Billing) CreateSubscription(userRepo user.UserRepo, subCreatedAttrs Sub func (b *Billing) CancelSubscription(userRepo user.UserRepo, email string) error { user, err := userRepo.GetUserByEmail(email) if err != nil { - log.Printf("repo.GetUserByEmail failed: %v", err) + b.Logger.Error("repo.GetUserByEmail failed", "error", err) return err } @@ -333,7 +332,7 @@ func (b *Billing) CancelSubscription(userRepo user.UserRepo, email string) error "cancelled", ) if err != nil { - log.Printf("CancelSubscriptionData failed: %v", err) + b.Logger.Error("CancelSubscriptionData failed", "error", err) return err } @@ -343,7 +342,7 @@ func (b *Billing) CancelSubscription(userRepo user.UserRepo, email string) error func (b *Billing) ResumeSubscription(userRepo user.UserRepo, email string) error { user, err := userRepo.GetUserByEmail(email) if err != nil { - log.Printf("repo.GetUserByEmail failed: %v", err) + b.Logger.Error("repo.GetUserByEmail failed", "error", err) return err } @@ -352,7 +351,7 @@ func (b *Billing) ResumeSubscription(userRepo user.UserRepo, email string) error "active", ) if err != nil { - log.Printf("CancelSubscriptionData failed: %v", err) + b.Logger.Error("CancelSubscriptionData failed", "error", err) return err } @@ -362,7 +361,7 @@ func (b *Billing) ResumeSubscription(userRepo user.UserRepo, email string) error func (b *Billing) ExpireSubscription(userRepo user.UserRepo, billingRepo BillingRepo, email string) error { user, err := userRepo.GetUserByEmail(email) if err != nil { - log.Printf("repo.GetUserByEmail failed: %v", err) + b.Logger.Error("repo.GetUserByEmail failed", "error", err) return err } @@ -371,14 +370,14 @@ func (b *Billing) ExpireSubscription(userRepo user.UserRepo, billingRepo Billing "expired", ) if err != nil { - log.Printf("CancelSubscriptionData failed: %v", err) + b.Logger.Error("CancelSubscriptionData failed", "error", err) return err } if user.SubscriptionCredits > 0 { err = userRepo.AddCredits(user.ID, -user.SubscriptionCredits, "subscription") if err != nil { - log.Printf("repo.AddCredits failed: %v", err) + b.Logger.Error("repo.AddCredits failed", "error", err) return err } @@ -389,7 +388,7 @@ func (b *Billing) ExpireSubscription(userRepo user.UserRepo, billingRepo Billing Reason: "Zeroed out credits on subscription expiration", } if err := billingRepo.LogCreditTransaction(tx); err != nil { - log.Printf("Warning: zero-out succeeded but failed to log transaction: %v", err) + b.Logger.Warn("Zero-out succeeded but failed to log transaction", "error", err) } } @@ -399,7 +398,7 @@ func (b *Billing) ExpireSubscription(userRepo user.UserRepo, billingRepo Billing func (b *Billing) RenewSubscription(userRepo user.UserRepo, billingRepo BillingRepo, subRenewAttrs SubscriptionRenewAttributes) error { user, err := userRepo.GetUserByEmail(subRenewAttrs.UserEmail) if err != nil { - log.Printf("repo.GetUserByEmail failed: %v", err) + b.Logger.Error("repo.GetUserByEmail failed", "error", err) return err } @@ -419,12 +418,12 @@ func (b *Billing) RenewSubscription(userRepo user.UserRepo, billingRepo BillingR credits = 20 reason = "Premium subscription monthly credit" default: - log.Printf("ERROR: unknown user.SubscriptionTier: %s", user.SubscriptionTier) + b.Logger.Error("unknown user.SubscriptionTier", "subscriptionTier", user.SubscriptionTier) return fmt.Errorf("unknown user.SubscriptionTier: %s", user.SubscriptionTier) } if err := userRepo.AddCredits(user.ID, credits, "subscription"); err != nil { - log.Printf("repo.AddCredits failed: %v", err) + b.Logger.Error("repo.AddCredits failed", "error", err) return err } @@ -435,7 +434,7 @@ func (b *Billing) RenewSubscription(userRepo user.UserRepo, billingRepo BillingR Reason: reason, } if err := billingRepo.LogCreditTransaction(tx); err != nil { - log.Printf("Warning: credit granted but failed to log transaction: %v", err) + b.Logger.Warn("credit granted but failed to log transaction", "error", err) return err } @@ -445,7 +444,7 @@ func (b *Billing) RenewSubscription(userRepo user.UserRepo, billingRepo BillingR func (b *Billing) ChangeSubscription(userRepo user.UserRepo, billingRepo BillingRepo, subChangedAttrs SubscriptionAttributes) error { user, err := userRepo.GetUserByEmail(subChangedAttrs.UserEmail) if err != nil { - log.Printf("repo.GetUserByEmail failed: %v", err) + b.Logger.Error("repo.GetUserByEmail failed", "error", err) return err } @@ -468,12 +467,12 @@ func (b *Billing) ChangeSubscription(userRepo user.UserRepo, billingRepo Billing credits = 10 reason = "Pro upgraded to Premium subscription monthly credit" default: - log.Printf("ERROR: unknown user.SubscriptionTier: %s", user.SubscriptionTier) + b.Logger.Error("unknown user.SubscriptionTier", "error", user.SubscriptionTier) return fmt.Errorf("unknown user.SubscriptionTier: %s", user.SubscriptionTier) } if err := userRepo.AddCredits(user.ID, credits, "subscription"); err != nil { - log.Printf("repo.AddCredits failed: %v", err) + b.Logger.Error("repo.AddCredits failed", "error", err) return err } @@ -484,7 +483,7 @@ func (b *Billing) ChangeSubscription(userRepo user.UserRepo, billingRepo Billing Reason: reason, } if err := billingRepo.LogCreditTransaction(tx); err != nil { - log.Printf("Warning: credit granted but failed to log transaction: %v", err) + b.Logger.Warn("credit granted but failed to log transaction", "error", err) return err } @@ -494,7 +493,7 @@ func (b *Billing) ChangeSubscription(userRepo user.UserRepo, billingRepo Billing func (b *Billing) UpdateSubscription(userRepo user.UserRepo, subUpdatedAttrs SubscriptionAttributes, subscriptionID string) error { user, err := userRepo.GetUserByEmail(subUpdatedAttrs.UserEmail) if err != nil { - log.Printf("repo.GetUserByEmail failed: %v", err) + b.Logger.Error("repo.GetUserByEmail failed", "error", err) return err } @@ -505,7 +504,7 @@ func (b *Billing) UpdateSubscription(userRepo user.UserRepo, subUpdatedAttrs Sub case b.VariantIDPremium: tier = "premium" default: - log.Printf("ERROR: unknown variantID: %d", subUpdatedAttrs.VariantID) + b.Logger.Error("unknown variantID", "variantID", subUpdatedAttrs.VariantID) return fmt.Errorf("unknown variant ID: %d", subUpdatedAttrs.VariantID) } @@ -518,7 +517,7 @@ func (b *Billing) UpdateSubscription(userRepo user.UserRepo, subUpdatedAttrs Sub subUpdatedAttrs.EndsAt, ) if err != nil { - log.Printf("UpdateSubscriptionData failed: %v", err) + b.Logger.Error("UpdateSubscriptionData failed", "error", err) return err } diff --git a/internal/server/server.go b/internal/server/server.go index cae0c30..d456027 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -36,8 +36,8 @@ func NewServer(logger *slog.Logger) (*Server, error) { conversationRepo := conversation.NewRepository(db) billingRepo := billing.NewRepository(db) openAI := chatgpt.NewOpenAI(logger) - mailer := mailer.NewMailer() - billing, err := billing.NewBilling() + mailer := mailer.NewMailer(logger) + billing, err := billing.NewBilling(logger) if err != nil { log.Printf("billing.NewBilling failed: %v", err) return nil, err diff --git a/mailer/model.go b/mailer/model.go index 0294e70..36170c3 100644 --- a/mailer/model.go +++ b/mailer/model.go @@ -1,10 +1,14 @@ package mailer -import "os" +import ( + "log/slog" + "os" +) type Mailer struct { APIKey string BaseURL string + Logger *slog.Logger } const signature = ` @@ -19,10 +23,11 @@ const signature = `

` -func NewMailer() *Mailer { +func NewMailer(logger *slog.Logger) *Mailer { return &Mailer{ APIKey: os.Getenv("RESEND_API_KEY"), BaseURL: "https://api.resend.com", + Logger: logger, } } diff --git a/mailer/service.go b/mailer/service.go index 01327bb..70c3b6e 100644 --- a/mailer/service.go +++ b/mailer/service.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/json" "fmt" - "log" "net/http" ) @@ -23,13 +22,13 @@ func (m *Mailer) SendPasswordReset(email, resetURL string) error { body, err := json.Marshal(payload) if err != nil { - log.Printf("Marshal failed: %v", err) + m.Logger.Error("Marshal failed", "error", err) return err } req, err := http.NewRequest("POST", fmt.Sprintf("%s/emails", m.BaseURL), bytes.NewBuffer(body)) if err != nil { - log.Printf("Mailer NewRequest failed: %v", err) + m.Logger.Error("Mailer NewRequest failed", "error", err) return err } req.Header.Set("Authorization", "Bearer "+m.APIKey) @@ -37,13 +36,13 @@ func (m *Mailer) SendPasswordReset(email, resetURL string) error { resp, err := http.DefaultClient.Do(req) if err != nil { - log.Printf("Mailer Client Do failed: %v", err) + m.Logger.Error("Mailer Client Do failed", "error", err) return err } defer resp.Body.Close() if resp.StatusCode >= 400 { - return fmt.Errorf("Resend error: %s", resp.Status) + return fmt.Errorf("resend error: %s", resp.Status) } return nil @@ -152,13 +151,13 @@ func (m *Mailer) SendDeletionConfirmation(email string) error { body, err := json.Marshal(payload) if err != nil { - log.Printf("Marshal failed: %v", err) + m.Logger.Error("Marshal failed", "error", err) return err } req, err := http.NewRequest("POST", fmt.Sprintf("%s/emails", m.BaseURL), bytes.NewBuffer(body)) if err != nil { - log.Printf("Mailer NewRequest failed: %v", err) + m.Logger.Error("Mailer NewRequest failed", "error", err) return err } req.Header.Set("Authorization", "Bearer "+m.APIKey) @@ -166,13 +165,13 @@ func (m *Mailer) SendDeletionConfirmation(email string) error { resp, err := http.DefaultClient.Do(req) if err != nil { - log.Printf("Mailer Client Do failed: %v", err) + m.Logger.Error("Mailer Client Do failed", "error", err) return err } defer resp.Body.Close() if resp.StatusCode >= 400 { - return fmt.Errorf("Resend error: %s", resp.Status) + return fmt.Errorf("resend error: %s", resp.Status) } return nil From a56255a2218500e1f7552335ba72c3e00df6be89 Mon Sep 17 00:00:00 2001 From: MichaelBoegner Date: Sun, 21 Sep 2025 14:48:31 +0700 Subject: [PATCH 06/15] added slog to TestMain and InitTestServer --- billing/repository_mock.go | 2 +- handlers/handlers_test.go | 33 +++++++++++++++++++++------------ internal/testutil/server.go | 16 ++++++++-------- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/billing/repository_mock.go b/billing/repository_mock.go index 9caba66..9ec7131 100644 --- a/billing/repository_mock.go +++ b/billing/repository_mock.go @@ -12,7 +12,7 @@ func NewMockRepo() *MockRepo { func (m *MockRepo) LogCreditTransaction(tx CreditTransaction) error { if m.FailLogCreditTransaction { - return errors.New("Mocked LogCreditTransaction failure") + return errors.New("mocked LogCreditTransaction failure") } return nil } diff --git a/handlers/handlers_test.go b/handlers/handlers_test.go index 28acbd4..e143cb2 100644 --- a/handlers/handlers_test.go +++ b/handlers/handlers_test.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "log" + "log/slog" "net/http" "os" "strings" @@ -53,33 +54,41 @@ var ( mockAI *mocks.MockOpenAIClient ) -func TestMain(m *testing.M) { - log.SetFlags(log.LstdFlags | log.Llongfile) +var logger *slog.Logger - log.Println("Loading environment variables...") - err := godotenv.Load("../.env.test") - if err != nil { - log.Fatalf("Error loading .env.test file: %v", err) +func TestMain(m *testing.M) { + handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ + Level: slog.LevelDebug, + }) + logger = slog.New(handler) + + logger.Info("Loading environment variables...") + if err := godotenv.Load("../.env.test"); err != nil { + logger.Error("failed to load .env.test", "error", err) + os.Exit(1) } - log.Println("Initializing test server...") - Handler, err = testutil.InitTestServer() + logger.Info("Initializing test server...") + var err error + Handler, err = testutil.InitTestServer(logger) if err != nil { - log.Fatalf("Test server initialization failed: %v", err) + logger.Error("test server initialization failed", "error", err) + os.Exit(1) } if testutil.TestServerURL == "" { - log.Fatal("TestMain: TestServerURL is empty! The server did not start properly.") + logger.Error("TestServerURL is empty! The server did not start properly") + os.Exit(1) } - log.Printf("TestMain: Test server started successfully at: %s", testutil.TestServerURL) + logger.Info("Test server started", "url", testutil.TestServerURL) mockAI = Handler.OpenAI.(*mocks.MockOpenAIClient) conversationBuilder = testutil.NewConversationBuilder() code := m.Run() - log.Println("Stopping test server...") + logger.Info("Stopping test server...") testutil.StopTestServer() os.Exit(code) diff --git a/internal/testutil/server.go b/internal/testutil/server.go index 8d547e2..5d29ffa 100644 --- a/internal/testutil/server.go +++ b/internal/testutil/server.go @@ -1,7 +1,7 @@ package testutil import ( - "log" + "log/slog" "net/http" "net/http/httptest" @@ -22,15 +22,15 @@ var ( TestServerURL string ) -func InitTestServer() (*handlers.Handler, error) { - log.Println("Initializing test database connection...") +func InitTestServer(logger *slog.Logger) (*handlers.Handler, error) { + logger.Info("Initializing test database connection...") db, err := database.StartDB() if err != nil { - log.Fatalf("Failed to connect to test database: %v", err) + logger.Error("Failed to connect to test database", "error", err) } - log.Println("Database connected successfully.") + logger.Info("Database connected successfully.") interviewRepo := interview.NewRepository(db) userRepo := user.NewRepository(db) @@ -41,11 +41,11 @@ func InitTestServer() (*handlers.Handler, error) { mailer := mocks.NewMockMailer() billing, err := billing.NewBilling() if err != nil { - log.Printf("billing.NewBilling failed: %v", err) + logger.Error("billing.NewBilling failed", "error", err) return nil, err } - handler := handlers.NewHandler(interviewRepo, userRepo, tokenRepo, conversationRepo, billingRepo, billing, mailer, openAI, db) + handler := handlers.NewHandler(interviewRepo, userRepo, tokenRepo, conversationRepo, billingRepo, billing, mailer, openAI, db, logger) TestMux = http.NewServeMux() TestMux.Handle("/api/users", http.HandlerFunc(handler.CreateUsersHandler)) @@ -154,7 +154,7 @@ func InitTestServer() (*handlers.Handler, error) { ), ) - log.Println("Starting in-memory test server...") + logger.Info("Starting in-memory test server...") TestServer = httptest.NewServer(TestMux) TestServerURL = TestServer.URL From dcd4f4fbb3f2bbcb4fd5410714ee70645e951b68 Mon Sep 17 00:00:00 2001 From: MichaelBoegner Date: Mon, 22 Sep 2025 16:35:23 +0700 Subject: [PATCH 07/15] added logger to NewBilling in InitTestServer. Also added NewMockOpenAIClient() instantiation for consistency. --- internal/mocks/openai_mock.go | 6 ++++++ internal/testutil/server.go | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/internal/mocks/openai_mock.go b/internal/mocks/openai_mock.go index e16832f..93bd72f 100644 --- a/internal/mocks/openai_mock.go +++ b/internal/mocks/openai_mock.go @@ -77,6 +77,12 @@ type MockOpenAIClient struct { Scenario string } +func NewMockOpenAIClient() *MockOpenAIClient { + mockOpenAIClient := &MockOpenAIClient{} + + return mockOpenAIClient +} + func (m *MockOpenAIClient) GetChatGPTResponse(prompt string) (*chatgpt.ChatGPTResponse, error) { return responseFixtures[ScenarioInterview], nil } diff --git a/internal/testutil/server.go b/internal/testutil/server.go index 5d29ffa..081bf42 100644 --- a/internal/testutil/server.go +++ b/internal/testutil/server.go @@ -37,9 +37,9 @@ func InitTestServer(logger *slog.Logger) (*handlers.Handler, error) { tokenRepo := token.NewRepository(db) conversationRepo := conversation.NewRepository(db) billingRepo := billing.NewRepository(db) - openAI := &mocks.MockOpenAIClient{} + openAI := mocks.NewMockOpenAIClient() mailer := mocks.NewMockMailer() - billing, err := billing.NewBilling() + billing, err := billing.NewBilling(logger) if err != nil { logger.Error("billing.NewBilling failed", "error", err) return nil, err From cfe844323b30e6cb70814fd9a904ace3b249588f Mon Sep 17 00:00:00 2001 From: MichaelBoegner Date: Mon, 22 Sep 2025 17:10:53 +0700 Subject: [PATCH 08/15] added slog to tests --- handlers/handlers_test.go | 55 +++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/handlers/handlers_test.go b/handlers/handlers_test.go index e143cb2..ce186ec 100644 --- a/handlers/handlers_test.go +++ b/handlers/handlers_test.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "io" - "log" "log/slog" "net/http" "os" @@ -20,7 +19,6 @@ import ( "github.com/michaelboegner/interviewer/internal/mocks" "github.com/michaelboegner/interviewer/internal/testutil" "github.com/michaelboegner/interviewer/interview" - "github.com/michaelboegner/interviewer/middleware" "github.com/michaelboegner/interviewer/token" "github.com/michaelboegner/interviewer/user" ) @@ -35,9 +33,7 @@ type TestCase struct { reqBody string headerKey string headerValue string - params middleware.AcceptedVals expectedStatus int - expectError bool respBody handlers.ReturnVals respBodyFunc func() handlers.ReturnVals Interview *interview.Interview @@ -63,7 +59,7 @@ func TestMain(m *testing.M) { logger = slog.New(handler) logger.Info("Loading environment variables...") - if err := godotenv.Load("../.env.test"); err != nil { + if err := godotenv.Load("../.env.dev"); err != nil { logger.Error("failed to load .env.test", "error", err) os.Exit(1) } @@ -157,18 +153,20 @@ func Test_RequestVerificationHandler_Integration(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var buf strings.Builder - log.SetOutput(&buf) + logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: true})) defer showLogsIfFail(t, tc.name, buf) // Act resp, respCode, err := testRequests(t, tc.headerKey, tc.headerValue, tc.method, tc.url, strings.NewReader(tc.reqBody)) if err != nil { - log.Fatalf("TestRequest for interview creation failed: %v", err) + logger.Error("TestRequest for interview creation failed", "error", err) + t.Fatalf("TestRequest failed: %v", err) } respUnmarshalled := &handlers.ReturnVals{} err = json.Unmarshal(resp, respUnmarshalled) if err != nil { + logger.Error("json.Unmarshal failed", "error", err) t.Fatalf("failed to unmarshal response: %v", err) } @@ -236,12 +234,12 @@ func Test_CreateUsersHandler_Integration(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var buf strings.Builder - log.SetOutput(&buf) + logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: true})) defer showLogsIfFail(t, tc.name, buf) verificationJWT, err := user.VerificationToken(tc.email, tc.username, tc.password) if err != nil { - log.Printf("GenerateEmailVerificationToken failed: %v", err) + logger.Error("GenerateEmailVerificationToken failed", "error", err) } reqBodyUser := strings.NewReader(fmt.Sprintf(`{ "token": "%s" @@ -250,12 +248,14 @@ func Test_CreateUsersHandler_Integration(t *testing.T) { // Act resp, respCode, err := testRequests(t, tc.headerKey, tc.headerValue, tc.method, tc.url, reqBodyUser) if err != nil { - log.Fatalf("TestRequest for interview creation failed: %v", err) + logger.Error("TestRequest for interview creation failed", "error", err) + t.Fatalf("TestRequest failed: %v", err) } respUnmarshalled := &handlers.ReturnVals{} err = json.Unmarshal(resp, respUnmarshalled) if err != nil { + logger.Error("json.Unmarshal failed", "error", err) t.Fatalf("failed to unmarshal response: %v", err) } @@ -338,18 +338,20 @@ func Test_GetUsersHandler_Integration(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var buf strings.Builder - log.SetOutput(&buf) + logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: true})) defer showLogsIfFail(t, tc.name, buf) // Act resp, respCode, err := testRequests(t, tc.headerKey, tc.headerValue, tc.method, tc.url, strings.NewReader(tc.reqBody)) if err != nil { - log.Fatalf("TestRequest for interview creation failed: %v", err) + logger.Error("TestRequest for interview creation failed", "error", err) + t.Fatalf("TestRequest failed: %v", err) } respUnmarshalled := &handlers.ReturnVals{} err = json.Unmarshal(resp, respUnmarshalled) if err != nil { + logger.Error("json.Unmarshal failed", "error", err) t.Fatalf("failed to unmarshal response: %v", err) } @@ -452,18 +454,20 @@ func Test_LoginHandler_Integration(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var buf strings.Builder - log.SetOutput(&buf) + logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: true})) defer showLogsIfFail(t, tc.name, buf) // Act resp, respCode, err := testRequests(t, tc.headerKey, tc.headerValue, tc.method, tc.url, strings.NewReader(tc.reqBody)) if err != nil { - log.Fatalf("TestRequest for interview creation failed: %v", err) + logger.Error("TestRequest for interview creation failed", "error", err) + t.Fatalf("TestRequest failed: %v", err) } respUnmarshalled := &handlers.ReturnVals{} err = json.Unmarshal(resp, respUnmarshalled) if err != nil { + logger.Error("json.Unmarshal failed", "error", err) t.Fatalf("failed to unmarshal response: %v", err) } @@ -587,18 +591,20 @@ func Test_RefreshTokensHandler_Integration(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var buf strings.Builder - log.SetOutput(&buf) + logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: true})) defer showLogsIfFail(t, tc.name, buf) // Act resp, respCode, err := testRequests(t, tc.headerKey, tc.headerValue, tc.method, tc.url, strings.NewReader(tc.reqBody)) if err != nil { - log.Fatalf("TestRequest for interview creation failed: %v", err) + logger.Error("TestRequest for interview creation failed", "error", err) + t.Fatalf("TestRequest failed: %v", err) } respUnmarshalled := &handlers.ReturnVals{} err = json.Unmarshal(resp, respUnmarshalled) if err != nil { + logger.Error("json.Unmarshal failed", "error", err) t.Fatalf("failed to unmarshal response: %v", err) } @@ -736,7 +742,7 @@ func Test_InterviewsHandler_Integration(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var buf strings.Builder - log.SetOutput(&buf) + logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: true})) defer showLogsIfFail(t, tc.name, buf) if tc.setup != nil { @@ -746,12 +752,14 @@ func Test_InterviewsHandler_Integration(t *testing.T) { // Act resp, respCode, err := testRequests(t, tc.headerKey, tc.headerValue, tc.method, tc.url, strings.NewReader(tc.reqBody)) if err != nil { - log.Fatalf("TestRequest for interview creation failed: %v", err) + logger.Error("TestRequest for interview creation failed", "error", err) + t.Fatalf("TestRequest failed: %v", err) } respUnmarshalled := &handlers.ReturnVals{} err = json.Unmarshal(resp, respUnmarshalled) if err != nil { + logger.Error("json.Unmarshal failed", "error", err) t.Fatalf("failed to unmarshal response: %v", err) } @@ -845,7 +853,7 @@ func Test_CreateConversationsHandler_Integration(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var buf strings.Builder - log.SetOutput(&buf) + logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: true})) defer showLogsIfFail(t, tc.name, buf) if tc.setup != nil { @@ -855,12 +863,14 @@ func Test_CreateConversationsHandler_Integration(t *testing.T) { // Act resp, respCode, err := testRequests(t, tc.headerKey, tc.headerValue, tc.method, tc.url, strings.NewReader(tc.reqBody)) if err != nil { - log.Fatalf("TestRequest for interview creation failed: %v", err) + logger.Error("TestRequest for interview creation failed", "error", err) + t.Fatalf("TestRequest failed: %v", err) } respUnmarshalled := &handlers.ReturnVals{} err = json.Unmarshal(resp, respUnmarshalled) if err != nil { + logger.Error("json.Unmarshal failed", "error", err) t.Fatalf("failed to unmarshal response: %v", err) } @@ -983,7 +993,7 @@ func Test_AppendConversationsHandler_Integration(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var buf strings.Builder - log.SetOutput(&buf) + logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: true})) defer showLogsIfFail(t, tc.name, buf) if tc.name == "AppendConversation_IsFinished" { @@ -1005,11 +1015,13 @@ func Test_AppendConversationsHandler_Integration(t *testing.T) { // Act resp, respCode, err := testRequests(t, tc.headerKey, tc.headerValue, tc.method, tc.url, strings.NewReader(tc.reqBody)) if err != nil { + logger.Error("TestRequest failed", "error", err) t.Fatalf("TestRequest failed: %v", err) } respUnmarshalled := &handlers.ReturnVals{} if err := json.Unmarshal(resp, respUnmarshalled); err != nil { + logger.Error("json.Unmarshal failed", "error", err) t.Fatalf("failed to unmarshal response: %v", err) } @@ -1042,7 +1054,6 @@ func Test_AppendConversationsHandler_Integration(t *testing.T) { } func showLogsIfFail(t *testing.T, name string, buf strings.Builder) { - log.SetOutput(os.Stderr) if t.Failed() { fmt.Printf("---- logs for test: %s ----\n%s\n", name, buf.String()) } From 61a83ad3ba7ae38730dad7674c67af2f834600af Mon Sep 17 00:00:00 2001 From: MichaelBoegner Date: Wed, 24 Sep 2025 16:42:08 +0700 Subject: [PATCH 09/15] added slog params to testutil.helpers.go --- internal/testutil/helpers.go | 40 ++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/internal/testutil/helpers.go b/internal/testutil/helpers.go index 652f5f4..50587d6 100644 --- a/internal/testutil/helpers.go +++ b/internal/testutil/helpers.go @@ -5,7 +5,7 @@ import ( "encoding/json" "fmt" "io" - "log" + "log/slog" "net/http" "os" "strconv" @@ -18,7 +18,7 @@ import ( "github.com/michaelboegner/interviewer/user" ) -func CreateTestUserAndJWT() (string, int) { +func CreateTestUserAndJWT(logger *slog.Logger) (string, int) { var ( jwt string userID int @@ -30,16 +30,16 @@ func CreateTestUserAndJWT() (string, int) { verificationJWT, err := user.VerificationToken(email, username, password) if err != nil { - log.Printf("GenerateEmailVerificationToken failed: %v", err) + logger.Error("GenerateEmailVerificationToken failed", "error", err) } reqBodyUser := strings.NewReader(fmt.Sprintf(`{ "token": "%s" }`, verificationJWT)) - _, err = testRequests("", "", "POST", TestServerURL+"/api/users", reqBodyUser) + _, err = testRequests("", "", "POST", TestServerURL+"/api/users", reqBodyUser, logger) if err != nil { - log.Printf("CreateTestUserAndJWT user creation failed: %v", err) + logger.Error("CreateTestUserAndJWT user creation failed", "error", err) } //test jwt retrieve @@ -50,9 +50,9 @@ func CreateTestUserAndJWT() (string, int) { } `) - resp, err := testRequests("", "", "POST", TestServerURL+"/api/auth/login", reqBodyLogin) + resp, err := testRequests("", "", "POST", TestServerURL+"/api/auth/login", reqBodyLogin, logger) if err != nil { - log.Printf("CreateTestUserAndJWT JWT creation failed: %v", err) + logger.Error("CreateTestUserAndJWT JWT creation failed", "error", err) } returnVals := &handlers.ReturnVals{} @@ -63,18 +63,18 @@ func CreateTestUserAndJWT() (string, int) { //test userID extract userID, err = token.ExtractUserIDFromToken(jwt) if err != nil { - log.Printf("CreateTestUserandJWT userID extraction failed: %v", err) + logger.Error("CreateTestUserandJWT userID extraction failed", "error", err) } return jwt, userID } -func CreateTestInterview(jwt string) int { +func CreateTestInterview(jwt string, logger *slog.Logger) int { reqBodyInterview := strings.NewReader(`{}`) - resp, err := testRequests("Authorization", "Bearer "+jwt, "POST", TestServerURL+"/api/interviews", reqBodyInterview) + resp, err := testRequests("Authorization", "Bearer "+jwt, "POST", TestServerURL+"/api/interviews", reqBodyInterview, logger) if err != nil { - log.Printf("CreateTestUserAndJWT JWT creation failed: %v", err) + logger.Error("CreateTestUserAndJWT JWT creation failed", "error", err) return 0 } @@ -84,16 +84,16 @@ func CreateTestInterview(jwt string) int { return returnVals.InterviewID } -func CreateTestConversation(jwt string, interviewID int) int { +func CreateTestConversation(jwt string, interviewID int, logger *slog.Logger) int { reqBodyConversation := strings.NewReader(`{ "conversation_id" : 1, "message" : "T1Q1A1" }`) reqURL := TestServerURL + fmt.Sprintf("/api/conversations/create/%d", interviewID) - resp, err := testRequests("Authorization", "Bearer "+jwt, "POST", reqURL, reqBodyConversation) + resp, err := testRequests("Authorization", "Bearer "+jwt, "POST", reqURL, reqBodyConversation, logger) if err != nil { - log.Printf("CreateTestUserAndJWT JWT creation failed: %v", err) + logger.Error("CreateTestUserAndJWT JWT creation failed", "error", err) return 0 } @@ -103,7 +103,7 @@ func CreateTestConversation(jwt string, interviewID int) int { return returnVals.Conversation.ID } -func CreateTestExpiredJWT(id, expires int) string { +func CreateTestExpiredJWT(id, expires int, logger *slog.Logger) string { var token *jwt.Token jwtSecret := os.Getenv("JWT_SECRET") key := []byte(jwtSecret) @@ -124,7 +124,7 @@ func CreateTestExpiredJWT(id, expires int) string { s, err := token.SignedString(key) if err != nil { - log.Printf("SignedString failed: %s", err) + logger.Error("SignedString failed", "error", err) return "" } @@ -138,12 +138,12 @@ func TruncateAllTables(db *sql.DB) error { return err } -func testRequests(headerKey, headerValue, method, url string, reqBody *strings.Reader) ([]byte, error) { +func testRequests(headerKey, headerValue, method, url string, reqBody *strings.Reader, logger *slog.Logger) ([]byte, error) { client := &http.Client{} req, err := http.NewRequest(method, url, reqBody) if err != nil { - log.Printf("CreateTestUserAndJWT user creation failed: %v", err) + logger.Error("CreateTestUserAndJWT user creation failed", "error", err) return nil, err } req.Header.Set("Content-Type", "application/json") @@ -153,14 +153,14 @@ func testRequests(headerKey, headerValue, method, url string, reqBody *strings.R resp, err := client.Do(req) if err != nil { - log.Printf("Request failed: %v", err) + logger.Error("Request failed", "error", err) return nil, err } defer resp.Body.Close() bodyBytes, err := io.ReadAll(resp.Body) if err != nil { - log.Printf("Reading response failed: %v", err) + logger.Error("Reading response failed", "error", err) return nil, err } From bed920b438f2d25c62f6792d5675d64edb9f48b6 Mon Sep 17 00:00:00 2001 From: MichaelBoegner Date: Fri, 26 Sep 2025 16:24:13 +0700 Subject: [PATCH 10/15] removed unnecessary logger in favor of t.logs for t.runs --- handlers/handlers_test.go | 76 ++++++--------------------------------- 1 file changed, 11 insertions(+), 65 deletions(-) diff --git a/handlers/handlers_test.go b/handlers/handlers_test.go index ce186ec..4515fa6 100644 --- a/handlers/handlers_test.go +++ b/handlers/handlers_test.go @@ -152,21 +152,15 @@ func Test_RequestVerificationHandler_Integration(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - var buf strings.Builder - logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: true})) - defer showLogsIfFail(t, tc.name, buf) - // Act resp, respCode, err := testRequests(t, tc.headerKey, tc.headerValue, tc.method, tc.url, strings.NewReader(tc.reqBody)) if err != nil { - logger.Error("TestRequest for interview creation failed", "error", err) t.Fatalf("TestRequest failed: %v", err) } respUnmarshalled := &handlers.ReturnVals{} err = json.Unmarshal(resp, respUnmarshalled) if err != nil { - logger.Error("json.Unmarshal failed", "error", err) t.Fatalf("failed to unmarshal response: %v", err) } @@ -233,13 +227,9 @@ func Test_CreateUsersHandler_Integration(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - var buf strings.Builder - logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: true})) - defer showLogsIfFail(t, tc.name, buf) - verificationJWT, err := user.VerificationToken(tc.email, tc.username, tc.password) if err != nil { - logger.Error("GenerateEmailVerificationToken failed", "error", err) + t.Fatalf("GenerateEmailVerificationToken failed: %v", err) } reqBodyUser := strings.NewReader(fmt.Sprintf(`{ "token": "%s" @@ -248,14 +238,12 @@ func Test_CreateUsersHandler_Integration(t *testing.T) { // Act resp, respCode, err := testRequests(t, tc.headerKey, tc.headerValue, tc.method, tc.url, reqBodyUser) if err != nil { - logger.Error("TestRequest for interview creation failed", "error", err) t.Fatalf("TestRequest failed: %v", err) } respUnmarshalled := &handlers.ReturnVals{} err = json.Unmarshal(resp, respUnmarshalled) if err != nil { - logger.Error("json.Unmarshal failed", "error", err) t.Fatalf("failed to unmarshal response: %v", err) } @@ -292,7 +280,7 @@ func Test_CreateUsersHandler_Integration(t *testing.T) { func Test_GetUsersHandler_Integration(t *testing.T) { cleanDBOrFail(t) - jwtoken, userID := testutil.CreateTestUserAndJWT() + jwtoken, userID := testutil.CreateTestUserAndJWT(logger) tests := []TestCase{ { @@ -337,21 +325,15 @@ func Test_GetUsersHandler_Integration(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - var buf strings.Builder - logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: true})) - defer showLogsIfFail(t, tc.name, buf) - // Act resp, respCode, err := testRequests(t, tc.headerKey, tc.headerValue, tc.method, tc.url, strings.NewReader(tc.reqBody)) if err != nil { - logger.Error("TestRequest for interview creation failed", "error", err) t.Fatalf("TestRequest failed: %v", err) } respUnmarshalled := &handlers.ReturnVals{} err = json.Unmarshal(resp, respUnmarshalled) if err != nil { - logger.Error("json.Unmarshal failed", "error", err) t.Fatalf("failed to unmarshal response: %v", err) } @@ -388,7 +370,7 @@ func Test_GetUsersHandler_Integration(t *testing.T) { func Test_LoginHandler_Integration(t *testing.T) { cleanDBOrFail(t) - _, _ = testutil.CreateTestUserAndJWT() + _, _ = testutil.CreateTestUserAndJWT(logger) tests := []TestCase{ { @@ -453,21 +435,15 @@ func Test_LoginHandler_Integration(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - var buf strings.Builder - logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: true})) - defer showLogsIfFail(t, tc.name, buf) - // Act resp, respCode, err := testRequests(t, tc.headerKey, tc.headerValue, tc.method, tc.url, strings.NewReader(tc.reqBody)) if err != nil { - logger.Error("TestRequest for interview creation failed", "error", err) t.Fatalf("TestRequest failed: %v", err) } respUnmarshalled := &handlers.ReturnVals{} err = json.Unmarshal(resp, respUnmarshalled) if err != nil { - logger.Error("json.Unmarshal failed", "error", err) t.Fatalf("failed to unmarshal response: %v", err) } @@ -515,7 +491,7 @@ func Test_LoginHandler_Integration(t *testing.T) { func Test_RefreshTokensHandler_Integration(t *testing.T) { cleanDBOrFail(t) - _, userID := testutil.CreateTestUserAndJWT() + _, userID := testutil.CreateTestUserAndJWT(logger) refreshToken, err := token.GetStoredRefreshToken(Handler.TokenRepo, userID) if err != nil { t.Fatalf("TC GetStoredRefreshToken failed: %v", err) @@ -590,21 +566,15 @@ func Test_RefreshTokensHandler_Integration(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - var buf strings.Builder - logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: true})) - defer showLogsIfFail(t, tc.name, buf) - // Act resp, respCode, err := testRequests(t, tc.headerKey, tc.headerValue, tc.method, tc.url, strings.NewReader(tc.reqBody)) if err != nil { - logger.Error("TestRequest for interview creation failed", "error", err) t.Fatalf("TestRequest failed: %v", err) } respUnmarshalled := &handlers.ReturnVals{} err = json.Unmarshal(resp, respUnmarshalled) if err != nil { - logger.Error("json.Unmarshal failed", "error", err) t.Fatalf("failed to unmarshal response: %v", err) } @@ -652,8 +622,8 @@ func Test_RefreshTokensHandler_Integration(t *testing.T) { func Test_InterviewsHandler_Integration(t *testing.T) { cleanDBOrFail(t) - jwtoken, userID := testutil.CreateTestUserAndJWT() - expiredJWT := testutil.CreateTestExpiredJWT(userID, -1) + jwtoken, userID := testutil.CreateTestUserAndJWT(logger) + expiredJWT := testutil.CreateTestExpiredJWT(userID, -1, logger) tests := []TestCase{ { @@ -741,10 +711,6 @@ func Test_InterviewsHandler_Integration(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - var buf strings.Builder - logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: true})) - defer showLogsIfFail(t, tc.name, buf) - if tc.setup != nil { tc.setup() } @@ -752,14 +718,12 @@ func Test_InterviewsHandler_Integration(t *testing.T) { // Act resp, respCode, err := testRequests(t, tc.headerKey, tc.headerValue, tc.method, tc.url, strings.NewReader(tc.reqBody)) if err != nil { - logger.Error("TestRequest for interview creation failed", "error", err) t.Fatalf("TestRequest failed: %v", err) } respUnmarshalled := &handlers.ReturnVals{} err = json.Unmarshal(resp, respUnmarshalled) if err != nil { - logger.Error("json.Unmarshal failed", "error", err) t.Fatalf("failed to unmarshal response: %v", err) } @@ -796,9 +760,9 @@ func Test_InterviewsHandler_Integration(t *testing.T) { func Test_CreateConversationsHandler_Integration(t *testing.T) { cleanDBOrFail(t) - jwtoken, _ := testutil.CreateTestUserAndJWT() + jwtoken, _ := testutil.CreateTestUserAndJWT(logger) mockAI.Scenario = mocks.ScenarioInterview - interviewID := testutil.CreateTestInterview(jwtoken) + interviewID := testutil.CreateTestInterview(jwtoken, logger) conversationsURL := testutil.TestServerURL + fmt.Sprintf("/api/conversations/create/%d", interviewID) tests := []TestCase{ @@ -852,10 +816,6 @@ func Test_CreateConversationsHandler_Integration(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - var buf strings.Builder - logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: true})) - defer showLogsIfFail(t, tc.name, buf) - if tc.setup != nil { tc.setup() } @@ -863,14 +823,12 @@ func Test_CreateConversationsHandler_Integration(t *testing.T) { // Act resp, respCode, err := testRequests(t, tc.headerKey, tc.headerValue, tc.method, tc.url, strings.NewReader(tc.reqBody)) if err != nil { - logger.Error("TestRequest for interview creation failed", "error", err) t.Fatalf("TestRequest failed: %v", err) } respUnmarshalled := &handlers.ReturnVals{} err = json.Unmarshal(resp, respUnmarshalled) if err != nil { - logger.Error("json.Unmarshal failed", "error", err) t.Fatalf("failed to unmarshal response: %v", err) } @@ -912,13 +870,13 @@ func Test_CreateConversationsHandler_Integration(t *testing.T) { func Test_AppendConversationsHandler_Integration(t *testing.T) { cleanDBOrFail(t) - jwtoken, _ := testutil.CreateTestUserAndJWT() + jwtoken, _ := testutil.CreateTestUserAndJWT(logger) mockAI.Scenario = mocks.ScenarioInterview - interviewID := testutil.CreateTestInterview(jwtoken) + interviewID := testutil.CreateTestInterview(jwtoken, logger) mockAI.Scenario = mocks.ScenarioCreated - conversationID := testutil.CreateTestConversation(jwtoken, interviewID) + conversationID := testutil.CreateTestConversation(jwtoken, interviewID, logger) urlTest := testutil.TestServerURL + fmt.Sprintf("/api/conversations/append/%d", interviewID) tests := []TestCase{ @@ -992,10 +950,6 @@ func Test_AppendConversationsHandler_Integration(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - var buf strings.Builder - logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: true})) - defer showLogsIfFail(t, tc.name, buf) - if tc.name == "AppendConversation_IsFinished" { reqBodyPre := fmt.Sprintf(`{ "conversation_id" : %d, @@ -1015,13 +969,11 @@ func Test_AppendConversationsHandler_Integration(t *testing.T) { // Act resp, respCode, err := testRequests(t, tc.headerKey, tc.headerValue, tc.method, tc.url, strings.NewReader(tc.reqBody)) if err != nil { - logger.Error("TestRequest failed", "error", err) t.Fatalf("TestRequest failed: %v", err) } respUnmarshalled := &handlers.ReturnVals{} if err := json.Unmarshal(resp, respUnmarshalled); err != nil { - logger.Error("json.Unmarshal failed", "error", err) t.Fatalf("failed to unmarshal response: %v", err) } @@ -1053,12 +1005,6 @@ func Test_AppendConversationsHandler_Integration(t *testing.T) { } } -func showLogsIfFail(t *testing.T, name string, buf strings.Builder) { - if t.Failed() { - fmt.Printf("---- logs for test: %s ----\n%s\n", name, buf.String()) - } -} - func cleanDBOrFail(t *testing.T) { if err := testutil.TruncateAllTables(Handler.DB); err != nil { t.Fatalf("Failed to clean DB: %v", err) From 574b56bc8d7a97c7a1db7c90d0b496e49fd18036 Mon Sep 17 00:00:00 2001 From: MichaelBoegner Date: Sat, 27 Sep 2025 16:04:20 +0700 Subject: [PATCH 11/15] added logger to StartServer() in main --- cmd/interviewer/main.go | 2 +- handlers/handlers.go | 2 +- internal/server/server.go | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/interviewer/main.go b/cmd/interviewer/main.go index 2627568..3a26c07 100644 --- a/cmd/interviewer/main.go +++ b/cmd/interviewer/main.go @@ -30,5 +30,5 @@ func main() { } logger.Info("starting server...") - srv.StartServer() + srv.StartServer(logger) } diff --git a/handlers/handlers.go b/handlers/handlers.go index fab1bdf..5bb5855 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -263,7 +263,7 @@ func (h *Handler) LoginHandler(w http.ResponseWriter, r *http.Request) { if params.Email == "" || params.Password == "" { h.Logger.Error("Invalid username or password.") - RespondWithError(w, http.StatusBadRequest, "Invalid username or password.") + RespondWithError(w, http.StatusBadRequest, "Authentication failed.") return } diff --git a/internal/server/server.go b/internal/server/server.go index d456027..608440a 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -39,7 +39,7 @@ func NewServer(logger *slog.Logger) (*Server, error) { mailer := mailer.NewMailer(logger) billing, err := billing.NewBilling(logger) if err != nil { - log.Printf("billing.NewBilling failed: %v", err) + logger.Error("billing.NewBilling failed", "error", err) return nil, err } @@ -154,7 +154,7 @@ func NewServer(logger *slog.Logger) (*Server, error) { return &Server{mux: mux}, nil } -func (s *Server) StartServer() { - log.Printf("Serving files from %s on port: %s\n", ".", "8080") +func (s *Server) StartServer(logger *slog.Logger) { + logger.Info("Serving files from %s on port: %s\n", ".", "8080") log.Fatal(http.ListenAndServe(":8080", middleware.EnableCors(s.mux))) } From 71a77ca04db9280fa521ca560d64a8114328fcf5 Mon Sep 17 00:00:00 2001 From: MichaelBoegner Date: Sat, 27 Sep 2025 16:34:02 +0700 Subject: [PATCH 12/15] fixed stray logs not yet using slog and clarified some informational logs --- billing/service.go | 4 ++-- handlers/handlers.go | 20 ++++++++++---------- handlers/helpers.go | 7 ++++--- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/billing/service.go b/billing/service.go index fb3fd41..8dc7d4f 100644 --- a/billing/service.go +++ b/billing/service.go @@ -212,7 +212,7 @@ func (b *Billing) ApplyCredits(userRepo user.UserRepo, billingRepo BillingRepo, creditType = "subscription" reason = "Premium subscription monthly credit grant" default: - b.Logger.Error("ERROR: unknown variantID", "error", variantID) + b.Logger.Error("ERROR: unknown variantID", "variantID", variantID) return fmt.Errorf("unknown variant ID: %d", variantID) } @@ -467,7 +467,7 @@ func (b *Billing) ChangeSubscription(userRepo user.UserRepo, billingRepo Billing credits = 10 reason = "Pro upgraded to Premium subscription monthly credit" default: - b.Logger.Error("unknown user.SubscriptionTier", "error", user.SubscriptionTier) + b.Logger.Error("unknown user.SubscriptionTier", "subscriptionTier", user.SubscriptionTier) return fmt.Errorf("unknown user.SubscriptionTier: %s", user.SubscriptionTier) } diff --git a/handlers/handlers.go b/handlers/handlers.go index ff6b9c0..364b0f5 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -63,7 +63,7 @@ func (h *Handler) RequestVerificationHandler(w http.ResponseWriter, r *http.Requ go func(email, url string) { if err := h.Mailer.SendVerificationEmail(email, url); err != nil { - log.Printf("SendVerificationEmail failed: %v", err) + h.Logger.Error("SendVerificationEmail failed", "error", err) } }(req.Email, verifyURL) @@ -132,14 +132,14 @@ func (h *Handler) CreateUsersHandler(w http.ResponseWriter, r *http.Request) { jwt, err := token.CreateJWT(strconv.Itoa(userCreated.ID), 0) if err != nil { - log.Printf("token.CreateJWT failed: %v", err) + h.Logger.Error("token.CreateJWT failed", "error", err) RespondWithError(w, http.StatusInternalServerError, "Internal server error") return } go func(email string) { if err := h.Mailer.SendWelcome(email); err != nil { - log.Printf("SendWelcome failed: %v", err) + h.Logger.Error("SendWelcome failed", "error", err) } }(userCreated.Email) @@ -164,7 +164,7 @@ func (h *Handler) GetUsersHandler(w http.ResponseWriter, r *http.Request) { return } - userIDParam, err := GetPathID(r, "/api/users/") + userIDParam, err := GetPathID(r, "/api/users/", h.Logger) if err != nil { h.Logger.Error("GetPathID error", "error", err) RespondWithError(w, http.StatusBadRequest, "UserID required") @@ -204,7 +204,7 @@ func (h *Handler) DeleteUserHandler(w http.ResponseWriter, r *http.Request) { return } - userIDParam, err := GetPathID(r, "/api/users/delete/") + userIDParam, err := GetPathID(r, "/api/users/delete/", h.Logger) if err != nil { h.Logger.Error("GetPathID error", "error", err) RespondWithError(w, http.StatusBadRequest, "UserID required") @@ -597,7 +597,7 @@ func (h *Handler) GetInterviewHandler(w http.ResponseWriter, r *http.Request) { return } - interviewID, err := GetPathID(r, "/api/interviews/") + interviewID, err := GetPathID(r, "/api/interviews/", h.Logger) if err != nil { h.Logger.Error("GetPathID failed", "error", err) RespondWithError(w, http.StatusBadRequest, "Invalid interview ID") @@ -635,7 +635,7 @@ func (h *Handler) UpdateInterviewStatusHandler(w http.ResponseWriter, r *http.Re return } - interviewID, err := GetPathID(r, "/api/interviews/") + interviewID, err := GetPathID(r, "/api/interviews/", h.Logger) if err != nil { RespondWithError(w, http.StatusBadRequest, "Invalid interview ID") return @@ -693,7 +693,7 @@ func (h *Handler) CreateConversationsHandler(w http.ResponseWriter, r *http.Requ return } - interviewID, err := GetPathID(r, "/api/conversations/create/") + interviewID, err := GetPathID(r, "/api/conversations/create/", h.Logger) if err != nil { h.Logger.Error("PathID error", "error", err) @@ -778,7 +778,7 @@ func (h *Handler) AppendConversationsHandler(w http.ResponseWriter, r *http.Requ return } - interviewID, err := GetPathID(r, "/api/conversations/append/") + interviewID, err := GetPathID(r, "/api/conversations/append/", h.Logger) if err != nil { h.Logger.Error("PathID error", "error", err) RespondWithError(w, http.StatusBadRequest, "Missing ID") @@ -847,7 +847,7 @@ func (h *Handler) GetConversationHandler(w http.ResponseWriter, r *http.Request) return } - interviewID, err := GetPathID(r, "/api/conversations/") + interviewID, err := GetPathID(r, "/api/conversations/", h.Logger) if err != nil { h.Logger.Error("PathID error", "error", err) RespondWithError(w, http.StatusBadRequest, "Missing ID") diff --git a/handlers/helpers.go b/handlers/helpers.go index 9bceae4..6adce44 100644 --- a/handlers/helpers.go +++ b/handlers/helpers.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "log" + "log/slog" "net/http" "strconv" "strings" @@ -31,19 +32,19 @@ func ValidateInterviewStatusTransition(currentStatus, nextStatus string) error { return fmt.Errorf("invalid state transition: %s → %s", currentStatus, nextStatus) } -func GetPathID(r *http.Request, prefix string) (int, error) { +func GetPathID(r *http.Request, prefix string, logger *slog.Logger) (int, error) { path := strings.TrimPrefix(r.URL.Path, prefix) path = strings.Trim(path, "/") if path == "" { - log.Printf("getPathID returned empty string") + logger.Error("getPathID returned empty string") err := errors.New("missing or invalid url param") return 0, err } id, err := strconv.Atoi(path) if err != nil { - log.Printf("getPathID failed: %v", err) + logger.Error("getPathID failed", "error", err) return 0, err } From 608b112296decbecd785fec86df3e079a088c856 Mon Sep 17 00:00:00 2001 From: MichaelBoegner Date: Sat, 27 Sep 2025 17:04:21 +0700 Subject: [PATCH 13/15] fixed tests --- billing/service_test.go | 30 +++++++++++++++++++----------- handlers/handlers_test.go | 8 ++++++++ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/billing/service_test.go b/billing/service_test.go index 5056966..07b13b9 100644 --- a/billing/service_test.go +++ b/billing/service_test.go @@ -5,6 +5,8 @@ import ( "crypto/sha256" "fmt" "log" + "log/slog" + "os" "strings" "testing" @@ -12,6 +14,20 @@ import ( "github.com/michaelboegner/interviewer/user" ) +func NewTestBilling() *billing.Billing { + handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ + Level: slog.LevelDebug, + }) + logger := slog.New(handler) + + return &billing.Billing{ + VariantIDIndividual: 1, + VariantIDPro: 2, + VariantIDPremium: 3, + Logger: logger, + } +} + func TestApplyCredits(t *testing.T) { tests := []struct { name string @@ -84,11 +100,7 @@ func TestApplyCredits(t *testing.T) { userRepo.FailAddCredits = tc.failCredit billingRepo.FailLogCreditTransaction = tc.failLog - b := billing.Billing{ - VariantIDIndividual: 1, - VariantIDPro: 2, - VariantIDPremium: 3, - } + b := NewTestBilling() err := b.ApplyCredits(userRepo, billingRepo, "test@example.com", tc.variantID) if tc.expectErr && err == nil { @@ -165,11 +177,7 @@ func TestDeductCredits(t *testing.T) { userRepo.FailAddCredits = tc.failCredit billingRepo.FailLogCreditTransaction = tc.failLog - b := billing.Billing{ - VariantIDIndividual: 1, - VariantIDPro: 2, - VariantIDPremium: 3, - } + b := NewTestBilling() attrs := billing.OrderAttributes{ UserEmail: "test@example.com", @@ -192,7 +200,7 @@ func TestDeductCredits(t *testing.T) { } func TestVerifyBillingSignature(t *testing.T) { - b := billing.Billing{} + b := NewTestBilling() body := []byte(`{"key":"value"}`) secret := "testsecret" mac := hmacSha256(body, secret) diff --git a/handlers/handlers_test.go b/handlers/handlers_test.go index 4515fa6..4ce5658 100644 --- a/handlers/handlers_test.go +++ b/handlers/handlers_test.go @@ -255,6 +255,14 @@ func Test_CreateUsersHandler_Integration(t *testing.T) { expected := tc.respBody got := *respUnmarshalled + // Assert JWT is non-empty + if got.JWToken == "" { + t.Fatalf("[%s] expected JWT, got empty string", tc.name) + } + + // Ignore JWToken when diffing + got.JWToken = "" + if diff := cmp.Diff(expected, got, cmpopts.EquateApproxTime(time.Second)); diff != "" { t.Errorf("Mismatch (-expected +got):\n%s", diff) } From d85fef4ed13768f7217023fadfd68139583bed5c Mon Sep 17 00:00:00 2001 From: MichaelBoegner Date: Sat, 27 Sep 2025 17:15:49 +0700 Subject: [PATCH 14/15] fixed syntax and inconsistencies --- billing/service.go | 2 +- handlers/handlers.go | 4 ++-- internal/server/server.go | 2 +- token/service.go | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/billing/service.go b/billing/service.go index 8dc7d4f..7f32790 100644 --- a/billing/service.go +++ b/billing/service.go @@ -71,7 +71,7 @@ func (b *Billing) RequestCheckoutSession(userEmail string, variantID int) (strin if res.StatusCode < 200 || res.StatusCode >= 300 { bodyBytes, _ := io.ReadAll(res.Body) b.Logger.Error("LemonSqueezy returned error", "error", string(bodyBytes)) - return "", fmt.Errorf("LemonSqueezy API error: %s", res.Status) + return "", fmt.Errorf("lemonSqueezy API error: %s", res.Status) } var result CheckoutResponse diff --git a/handlers/handlers.go b/handlers/handlers.go index 364b0f5..8c34f36 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -283,7 +283,7 @@ func (h *Handler) LoginHandler(w http.ResponseWriter, r *http.Request) { RespondWithError(w, http.StatusUnauthorized, user.ErrAccountDeleted.Error()) return } - RespondWithError(w, http.StatusUnauthorized, "Invalid username or password.") + RespondWithError(w, http.StatusUnauthorized, "Authentication failed.") return } @@ -457,7 +457,7 @@ func (h *Handler) RefreshTokensHandler(w http.ResponseWriter, r *http.Request) { if params.UserID == 0 { h.Logger.Error("Invalid userID") - RespondWithError(w, http.StatusBadRequest, "Invalid username or password") + RespondWithError(w, http.StatusBadRequest, "Authentication failed") return } diff --git a/internal/server/server.go b/internal/server/server.go index 608440a..ec7f53c 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -155,6 +155,6 @@ func NewServer(logger *slog.Logger) (*Server, error) { } func (s *Server) StartServer(logger *slog.Logger) { - logger.Info("Serving files from %s on port: %s\n", ".", "8080") + logger.Info("Serving files", "directory", ".", "port", "8080") log.Fatal(http.ListenAndServe(":8080", middleware.EnableCors(s.mux))) } diff --git a/token/service.go b/token/service.go index 22e8bae..f1c07f5 100644 --- a/token/service.go +++ b/token/service.go @@ -97,7 +97,7 @@ func ExtractUserIDFromToken(tokenString string) (int, error) { token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(tokenString *jwt.Token) (interface{}, error) { if _, ok := tokenString.Method.(*jwt.SigningMethodHMAC); !ok { - return 0, fmt.Errorf("Unauthorized") + return 0, fmt.Errorf("unauthorized") } return []byte(jwtSecret), nil }) @@ -116,5 +116,5 @@ func ExtractUserIDFromToken(tokenString string) (int, error) { return userID, nil } - return 0, fmt.Errorf("Unauthorized") + return 0, fmt.Errorf("unauthorized") } From a13036c7cd3136cdc0382758c0fed2d2ca2beabe Mon Sep 17 00:00:00 2001 From: MichaelBoegner Date: Sun, 28 Sep 2025 11:32:57 +0700 Subject: [PATCH 15/15] fixed loading env.test instead of env.dev --- handlers/handlers_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/handlers_test.go b/handlers/handlers_test.go index 4ce5658..7e1d311 100644 --- a/handlers/handlers_test.go +++ b/handlers/handlers_test.go @@ -59,7 +59,7 @@ func TestMain(m *testing.M) { logger = slog.New(handler) logger.Info("Loading environment variables...") - if err := godotenv.Load("../.env.dev"); err != nil { + if err := godotenv.Load("../.env.test"); err != nil { logger.Error("failed to load .env.test", "error", err) os.Exit(1) }