first commit
This commit is contained in:
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user