package handler import ( "fmt" "net/http" "portfolio-tracker/internal/model" "portfolio-tracker/internal/web/templates" "strconv" "strings" "time" "github.com/a-h/templ" ) func PortfolioHandler(w http.ResponseWriter, r *http.Request) { auth, username, err := getSessionInfo(r) if err != nil { fmt.Printf("Session error in PortfolioHandler: %v\n", err) } portfolios := getUserPortfolios(username) component := templates.Portfolio(auth, username, portfolios) templ.Handler(component).ServeHTTP(w, r) } func PortfolioDetailHandler(w http.ResponseWriter, r *http.Request) { auth, username, err := getSessionInfo(r) if err != nil { fmt.Printf("Session error in PortfolioDetailHandler: %v\n", err) } // Extract portfolio ID from URL path path := r.URL.Path portfolioIDStr := strings.TrimPrefix(path, "/portfolio/") portfolioID, err := strconv.ParseUint(portfolioIDStr, 10, 32) if err != nil { http.Error(w, "Ungültige Portfolio-ID", http.StatusBadRequest) return } // Get user var user model.User if err := DB.Where("username = ?", username).First(&user).Error; err != nil { http.Error(w, "Benutzer nicht gefunden", http.StatusUnauthorized) return } // Get portfolio with activities var portfolio model.Portfolio if err := DB.Preload("Activities").Where("id = ? AND user_id = ?", portfolioID, user.ID).First(&portfolio).Error; err != nil { http.Error(w, "Portfolio nicht gefunden oder keine Berechtigung", http.StatusForbidden) return } // Get all user portfolios for navigation portfolios := getUserPortfolios(username) component := templates.PortfolioDetail(auth, username, portfolio, portfolios) templ.Handler(component).ServeHTTP(w, r) } func CreatePortfolioHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Nur POST erlaubt", http.StatusMethodNotAllowed) return } auth, username, err := getSessionInfo(r) if err != nil { fmt.Printf("Session error in CreatePortfolioHandler: %v\n", err) } if !auth { http.Error(w, "Nicht angemeldet", http.StatusUnauthorized) return } if username == "" { http.Error(w, "Benutzer nicht gefunden", http.StatusBadRequest) return } // Get form values name := r.FormValue("name") baseCurrency := r.FormValue("base_currency") description := r.FormValue("description") // Basic validation if name == "" || baseCurrency == "" { http.Error(w, "Name und Basiswährung sind erforderlich", http.StatusBadRequest) return } // Get user from database var user model.User if err := DB.Where("username = ?", username).First(&user).Error; err != nil { http.Error(w, "Benutzer nicht gefunden", http.StatusBadRequest) return } // Create new portfolio portfolio := model.Portfolio{ UserID: user.ID, Name: name, BaseCurrency: baseCurrency, Description: description, } if err := DB.Create(&portfolio).Error; err != nil { fmt.Printf("Error creating portfolio: %v\n", err) http.Error(w, "Fehler beim Erstellen des Portfolios", http.StatusInternalServerError) return } fmt.Printf("Portfolio created successfully: ID=%d, Name=%s, User=%s\n", portfolio.ID, portfolio.Name, username) // Redirect to the new portfolio http.Redirect(w, r, fmt.Sprintf("/portfolio/%d", portfolio.ID), http.StatusSeeOther) } func PortfolioTransactionHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Nur POST erlaubt", http.StatusMethodNotAllowed) return } auth, username, err := getSessionInfo(r) if err != nil { fmt.Printf("Session error in PortfolioTransactionHandler: %v\n", err) } if !auth { http.Error(w, "Nicht angemeldet", http.StatusUnauthorized) return } if username == "" { http.Error(w, "Benutzer nicht gefunden", http.StatusBadRequest) return } // Get form values portfolioIDStr := r.FormValue("portfolio_id") transactionType := r.FormValue("type") stock := r.FormValue("stock") amountStr := r.FormValue("amount") priceStr := r.FormValue("price") dateStr := r.FormValue("date") note := r.FormValue("note") // Basic validation if transactionType == "" || stock == "" || amountStr == "" || priceStr == "" || dateStr == "" { http.Error(w, "Alle Pflichtfelder müssen ausgefüllt werden", http.StatusBadRequest) return } // Parse form values amount, err := strconv.ParseFloat(amountStr, 64) if err != nil { http.Error(w, "Ungültiger Betrag", http.StatusBadRequest) return } price, err := strconv.ParseFloat(priceStr, 64) if err != nil { http.Error(w, "Ungültiger Preis", http.StatusBadRequest) return } date, err := time.Parse("2006-01-02", dateStr) if err != nil { http.Error(w, "Ungültiges Datum", http.StatusBadRequest) return } portfolioID, err := strconv.ParseUint(portfolioIDStr, 10, 32) if err != nil { http.Error(w, "Ungültige Portfolio-ID", http.StatusBadRequest) return } // Verify portfolio belongs to user var portfolio model.Portfolio if err := DB.Joins("User").Where("portfolios.id = ? AND User.username = ?", portfolioID, username).First(&portfolio).Error; err != nil { http.Error(w, "Portfolio nicht gefunden oder keine Berechtigung", http.StatusBadRequest) return } // Fetch stock currency from Yahoo Finance stockCurrency, err := getStockCurrency(stock) if err != nil { fmt.Printf("Warning: Could not fetch currency for stock %s: %v. Using portfolio base currency.\n", stock, err) stockCurrency = portfolio.BaseCurrency } fmt.Printf("Stock %s currency: %s\n", stock, stockCurrency) // Create activity activity := model.Activity{ PortfolioID: uint(portfolioID), Stock: stock, Type: model.ActivityType(transactionType), Amount: amount, Price: price, Currency: stockCurrency, // Use the fetched stock currency Date: date, Note: note, } if err := DB.Create(&activity).Error; err != nil { fmt.Printf("Error creating activity: %v\n", err) http.Error(w, "Fehler beim Speichern der Transaktion", http.StatusInternalServerError) return } fmt.Printf("Activity created successfully: ID=%d, Type=%s, Stock=%s, Currency=%s, Portfolio=%d\n", activity.ID, activity.Type, activity.Stock, activity.Currency, activity.PortfolioID) // Redirect to portfolio page http.Redirect(w, r, fmt.Sprintf("/portfolio/%d", portfolioID), http.StatusSeeOther) } func EditTransactionHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Nur POST erlaubt", http.StatusMethodNotAllowed) return } auth, username, err := getSessionInfo(r) if err != nil { fmt.Printf("Session error in EditTransactionHandler: %v\n", err) } if !auth { http.Error(w, "Nicht angemeldet", http.StatusUnauthorized) return } if username == "" { http.Error(w, "Benutzer nicht gefunden", http.StatusBadRequest) return } // Get form values activityIDStr := r.FormValue("activity_id") portfolioIDStr := r.FormValue("portfolio_id") transactionType := r.FormValue("type") stock := r.FormValue("stock") amountStr := r.FormValue("amount") priceStr := r.FormValue("price") dateStr := r.FormValue("date") note := r.FormValue("note") // Basic validation if activityIDStr == "" || transactionType == "" || stock == "" || amountStr == "" || priceStr == "" || dateStr == "" { http.Error(w, "Alle Pflichtfelder müssen ausgefüllt werden", http.StatusBadRequest) return } // Parse form values activityID, err := strconv.ParseUint(activityIDStr, 10, 32) if err != nil { http.Error(w, "Ungültige Aktivitäts-ID", http.StatusBadRequest) return } portfolioID, err := strconv.ParseUint(portfolioIDStr, 10, 32) if err != nil { http.Error(w, "Ungültige Portfolio-ID", http.StatusBadRequest) return } amount, err := strconv.ParseFloat(amountStr, 64) if err != nil { http.Error(w, "Ungültiger Betrag", http.StatusBadRequest) return } price, err := strconv.ParseFloat(priceStr, 64) if err != nil { http.Error(w, "Ungültiger Preis", http.StatusBadRequest) return } date, err := time.Parse("2006-01-02", dateStr) if err != nil { http.Error(w, "Ungültiges Datum", http.StatusBadRequest) return } // Get existing activity and verify ownership using subquery var activity model.Activity if err := DB.Where("id = ? AND portfolio_id IN (SELECT id FROM portfolios WHERE user_id = (SELECT id FROM users WHERE username = ?))", activityID, username).First(&activity).Error; err != nil { http.Error(w, "Aktivität nicht gefunden oder keine Berechtigung", http.StatusBadRequest) return } // Verify the activity belongs to the specified portfolio if activity.PortfolioID != uint(portfolioID) { http.Error(w, "Aktivität gehört nicht zu diesem Portfolio", http.StatusBadRequest) return } // Get portfolio to access base currency var portfolio model.Portfolio if err := DB.Where("id = ?", portfolioID).First(&portfolio).Error; err != nil { http.Error(w, "Portfolio nicht gefunden", http.StatusBadRequest) return } // Fetch stock currency from Yahoo Finance (or use portfolio base currency as fallback) stockCurrency, err := getStockCurrency(stock) if err != nil { fmt.Printf("Warning: Could not fetch currency for stock %s: %v. Using portfolio base currency.\n", stock, err) stockCurrency = portfolio.BaseCurrency } // Update activity fields activity.Type = model.ActivityType(transactionType) activity.Stock = stock activity.Amount = amount activity.Price = price activity.Currency = stockCurrency activity.Date = date activity.Note = note // Save updated activity if err := DB.Save(&activity).Error; err != nil { fmt.Printf("Error updating activity: %v\n", err) http.Error(w, "Fehler beim Aktualisieren der Transaktion", http.StatusInternalServerError) return } fmt.Printf("Activity updated successfully: ID=%d, Type=%s, Stock=%s, Portfolio=%d\n", activity.ID, activity.Type, activity.Stock, activity.PortfolioID) // Redirect to portfolio page http.Redirect(w, r, fmt.Sprintf("/portfolio/%d", portfolioID), http.StatusSeeOther) } func DeleteTransactionHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Nur POST erlaubt", http.StatusMethodNotAllowed) return } auth, username, err := getSessionInfo(r) if err != nil { fmt.Printf("Session error in DeleteTransactionHandler: %v\n", err) } if !auth { http.Error(w, "Nicht angemeldet", http.StatusUnauthorized) return } if username == "" { http.Error(w, "Benutzer nicht gefunden", http.StatusBadRequest) return } // Get form values activityIDStr := r.FormValue("activity_id") portfolioIDStr := r.FormValue("portfolio_id") // Basic validation if activityIDStr == "" || portfolioIDStr == "" { http.Error(w, "Aktivitäts-ID und Portfolio-ID sind erforderlich", http.StatusBadRequest) return } // Parse form values activityID, err := strconv.ParseUint(activityIDStr, 10, 32) if err != nil { http.Error(w, "Ungültige Aktivitäts-ID", http.StatusBadRequest) return } portfolioID, err := strconv.ParseUint(portfolioIDStr, 10, 32) if err != nil { http.Error(w, "Ungültige Portfolio-ID", http.StatusBadRequest) return } // Get existing activity and verify ownership using subquery var activity model.Activity if err := DB.Where("id = ? AND portfolio_id IN (SELECT id FROM portfolios WHERE user_id = (SELECT id FROM users WHERE username = ?))", activityID, username).First(&activity).Error; err != nil { http.Error(w, "Aktivität nicht gefunden oder keine Berechtigung", http.StatusBadRequest) return } // Verify the activity belongs to the specified portfolio if activity.PortfolioID != uint(portfolioID) { http.Error(w, "Aktivität gehört nicht zu diesem Portfolio", http.StatusBadRequest) return } // Delete the activity if err := DB.Delete(&activity).Error; err != nil { fmt.Printf("Error deleting activity: %v\n", err) http.Error(w, "Fehler beim Löschen der Transaktion", http.StatusInternalServerError) return } fmt.Printf("Activity deleted successfully: ID=%d, Type=%s, Stock=%s, Portfolio=%d\n", activity.ID, activity.Type, activity.Stock, activity.PortfolioID) // Redirect to portfolio page http.Redirect(w, r, fmt.Sprintf("/portfolio/%d", portfolioID), http.StatusSeeOther) }