288 lines
7.8 KiB
Go
288 lines
7.8 KiB
Go
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
|
|
}
|