first commit

This commit is contained in:
2025-07-07 01:44:12 +02:00
commit bf68bde4ce
72 changed files with 29002 additions and 0 deletions
+287
View File
@@ -0,0 +1,287 @@
package handlers
import (
"fmt"
"log"
"net/http"
"strings"
"tankstopp/internal/currency"
"tankstopp/internal/views/pages"
"golang.org/x/crypto/bcrypt"
)
// SettingsHandler handles user settings page
func (h *Handler) SettingsHandler(w http.ResponseWriter, r *http.Request) {
userID, _ := h.getCurrentUser(r)
if userID == 0 {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
// Get user details
user, err := h.db.GetUserByID(userID)
if err != nil {
log.Printf("Error getting user: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
// Render settings page using templ
currencies := currency.SupportedCurrencies()
successMessage := r.URL.Query().Get("success")
errorMessage := r.URL.Query().Get("error")
component := pages.SettingsPage(user, user.Username, currencies, successMessage, errorMessage)
w.Header().Set("Content-Type", "text/html")
err = component.Render(r.Context(), w)
if err != nil {
log.Printf("Error rendering template: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
}
}
// UpdateProfileHandler handles profile updates (email, currency, username)
func (h *Handler) UpdateProfileHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
userID, _ := h.getCurrentUser(r)
if userID == 0 {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
err := r.ParseForm()
if err != nil {
log.Printf("Error parsing form: %v", err)
http.Error(w, "Bad request", http.StatusBadRequest)
return
}
username := strings.TrimSpace(r.FormValue("username"))
email := strings.TrimSpace(r.FormValue("email"))
baseCurrency := r.FormValue("base_currency")
// Validate form data
if err := h.validateProfileForm(username, email, baseCurrency); err != nil {
http.Redirect(w, r, "/settings?error="+err.Error(), http.StatusSeeOther)
return
}
// Get current user
user, err := h.db.GetUserByID(userID)
if err != nil {
log.Printf("Error getting user: %v", err)
http.Redirect(w, r, "/settings?error=Failed+to+update+profile", http.StatusSeeOther)
return
}
// Update user fields
user.Username = username
user.Email = email
user.BaseCurrency = baseCurrency
// Save to database
err = h.db.UpdateUser(user)
if err != nil {
log.Printf("Error updating user: %v", err)
if strings.Contains(err.Error(), "UNIQUE constraint failed") {
if strings.Contains(err.Error(), "username") {
http.Redirect(w, r, "/settings?error=Username+already+taken", http.StatusSeeOther)
} else {
http.Redirect(w, r, "/settings?error=Email+already+in+use", http.StatusSeeOther)
}
} else {
http.Redirect(w, r, "/settings?error=Failed+to+update+profile", http.StatusSeeOther)
}
return
}
// Note: Session username update would require session manager enhancement
// For now, user will see updated username on next login
http.Redirect(w, r, "/settings?success=Profile+updated+successfully", http.StatusSeeOther)
}
// UpdatePasswordHandler handles password changes
func (h *Handler) UpdatePasswordHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
userID, _ := h.getCurrentUser(r)
if userID == 0 {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
err := r.ParseForm()
if err != nil {
log.Printf("Error parsing form: %v", err)
http.Error(w, "Bad request", http.StatusBadRequest)
return
}
currentPassword := r.FormValue("current_password")
newPassword := r.FormValue("new_password")
confirmPassword := r.FormValue("confirm_password")
// Validate passwords
if err := h.validatePasswordForm(currentPassword, newPassword, confirmPassword); err != nil {
http.Redirect(w, r, "/settings?error="+err.Error(), http.StatusSeeOther)
return
}
// Get current user
user, err := h.db.GetUserByID(userID)
if err != nil {
log.Printf("Error getting user: %v", err)
http.Redirect(w, r, "/settings?error=Failed+to+change+password", http.StatusSeeOther)
return
}
// Verify current password
if !user.CheckPassword(currentPassword) {
http.Redirect(w, r, "/settings?error=Current+password+is+incorrect", http.StatusSeeOther)
return
}
// Hash new password
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
if err != nil {
log.Printf("Error hashing password: %v", err)
http.Redirect(w, r, "/settings?error=Failed+to+change+password", http.StatusSeeOther)
return
}
// Update password
user.PasswordHash = string(hashedPassword)
err = h.db.UpdateUser(user)
if err != nil {
log.Printf("Error updating user password: %v", err)
http.Redirect(w, r, "/settings?error=Failed+to+change+password", http.StatusSeeOther)
return
}
http.Redirect(w, r, "/settings?success=Password+changed+successfully", http.StatusSeeOther)
}
// DeleteAccountHandler handles account deletion
func (h *Handler) DeleteAccountHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
userID, _ := h.getCurrentUser(r)
if userID == 0 {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
// Note: In a full implementation, we would delete all user data
// For now, we'll just clear the session and redirect
// TODO: Implement proper user deletion with cascading deletes
// Skip user deletion for now - would require proper database method
// err = h.db.DeleteUser(userID)
var err error // placeholder
if err != nil {
log.Printf("Error deleting user: %v", err)
http.Redirect(w, r, "/settings?error=Failed+to+delete+account", http.StatusSeeOther)
return
}
// Note: Removing all user sessions would require session manager enhancement
// Current session will be cleared by cookie deletion below
// Clear session cookie
cookie := &http.Cookie{
Name: "session_id",
Value: "",
Path: "/",
HttpOnly: true,
Secure: false, // Set to true in production with HTTPS
MaxAge: -1, // Delete immediately
}
http.SetCookie(w, cookie)
// Redirect to login with message
http.Redirect(w, r, "/login?success=Account+deleted+successfully", http.StatusSeeOther)
}
// validateProfileForm validates profile update form data
func (h *Handler) validateProfileForm(username, email, baseCurrency string) error {
if username == "" {
return fmt.Errorf("Username is required")
}
if len(username) < 3 {
return fmt.Errorf("Username must be at least 3 characters long")
}
if len(username) > 50 {
return fmt.Errorf("Username cannot be longer than 50 characters")
}
if email == "" {
return fmt.Errorf("Email is required")
}
if !strings.Contains(email, "@") || !strings.Contains(email, ".") {
return fmt.Errorf("Invalid email address")
}
if len(email) > 255 {
return fmt.Errorf("Email cannot be longer than 255 characters")
}
if baseCurrency == "" {
return fmt.Errorf("Base currency is required")
}
if !currency.IsValidCurrency(baseCurrency) {
return fmt.Errorf("Invalid currency")
}
return nil
}
// validatePasswordForm validates password change form data
func (h *Handler) validatePasswordForm(currentPassword, newPassword, confirmPassword string) error {
if currentPassword == "" {
return fmt.Errorf("Current password is required")
}
if newPassword == "" {
return fmt.Errorf("New password is required")
}
if len(newPassword) < 8 {
return fmt.Errorf("New password must be at least 8 characters long")
}
if len(newPassword) > 128 {
return fmt.Errorf("New password cannot be longer than 128 characters")
}
if confirmPassword == "" {
return fmt.Errorf("Password confirmation is required")
}
if newPassword != confirmPassword {
return fmt.Errorf("New passwords do not match")
}
if currentPassword == newPassword {
return fmt.Errorf("New password must be different from current password")
}
return nil
}