first commit

This commit is contained in:
Matthias Hinrichs
2025-07-05 03:10:41 +02:00
commit 9b7bdcbc53
39 changed files with 5109 additions and 0 deletions
+413
View File
@@ -0,0 +1,413 @@
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)
}