1965 lines
55 KiB
Go
1965 lines
55 KiB
Go
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
"whereismymoney/internal/models"
|
|
"whereismymoney/internal/views"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/gorilla/sessions"
|
|
"golang.org/x/crypto/bcrypt"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type Handler struct {
|
|
DB *gorm.DB
|
|
Store *sessions.CookieStore
|
|
}
|
|
|
|
func NewHandler(db *gorm.DB) *Handler {
|
|
store := sessions.NewCookieStore([]byte("your-secret-key-here"))
|
|
|
|
// Session-Optionen für Safari-Kompatibilität
|
|
store.Options = &sessions.Options{
|
|
Path: "/",
|
|
MaxAge: 86400 * 7, // 7 Tage
|
|
HttpOnly: true,
|
|
Secure: false, // Für localhost Development
|
|
SameSite: http.SameSiteLaxMode, // Safari-kompatibel
|
|
}
|
|
|
|
return &Handler{
|
|
DB: db,
|
|
Store: store,
|
|
}
|
|
}
|
|
|
|
// Dashboard zeigt das Hauptdashboard (nur für angemeldete Benutzer)
|
|
func (h *Handler) Dashboard(c *gin.Context) {
|
|
// Session überprüfen
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
userID, ok := session.Values["user_id"]
|
|
if !ok {
|
|
c.Redirect(http.StatusSeeOther, "/login?error=required")
|
|
return
|
|
}
|
|
|
|
// Benutzer aus der Datenbank laden
|
|
var user models.User
|
|
if err := h.DB.First(&user, userID).Error; err != nil {
|
|
c.Redirect(http.StatusSeeOther, "/login?error=required")
|
|
return
|
|
}
|
|
|
|
// Testdaten generieren falls nötig
|
|
h.generateTestData(userID.(uint))
|
|
|
|
// Wiederkehrende Transaktionen generieren
|
|
h.processRecurringTransactions(userID.(uint))
|
|
|
|
// Dashboard-Daten sammeln
|
|
dashboardData, err := h.generateDashboardData(userID.(uint))
|
|
if err != nil {
|
|
c.String(http.StatusInternalServerError, "Fehler beim Laden der Dashboard-Daten: %v", err)
|
|
return
|
|
}
|
|
|
|
// Dashboard rendern
|
|
component := views.Dashboard(dashboardData)
|
|
component.Render(c.Request.Context(), c.Writer)
|
|
}
|
|
|
|
// DashboardDataAPI liefert Dashboard-Daten als JSON für AJAX-Anfragen
|
|
func (h *Handler) DashboardDataAPI(c *gin.Context) {
|
|
// Session überprüfen
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
userID, ok := session.Values["user_id"]
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Nicht angemeldet"})
|
|
return
|
|
}
|
|
|
|
// Testdaten generieren falls nötig
|
|
h.generateTestData(userID.(uint))
|
|
|
|
// Wiederkehrende Transaktionen generieren
|
|
h.processRecurringTransactions(userID.(uint))
|
|
|
|
// Dashboard-Daten generieren
|
|
data, err := h.generateDashboardData(userID.(uint))
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Fehler beim Laden der Dashboard-Daten"})
|
|
return
|
|
}
|
|
|
|
// Daten als JSON zurückgeben
|
|
c.JSON(http.StatusOK, data)
|
|
}
|
|
|
|
// generateDashboardData sammelt alle Daten für das Dashboard
|
|
func (h *Handler) generateDashboardData(userID uint) (*models.DashboardData, error) {
|
|
data := &models.DashboardData{}
|
|
|
|
// Wiederkehrende Transaktionen verarbeiten BEVOR Dashboard-Daten generiert werden
|
|
if err := h.processRecurringTransactions(userID); err != nil {
|
|
// Fehler loggen, aber nicht den gesamten Dashboard-Aufruf fehlschlagen lassen
|
|
fmt.Printf("Fehler beim Verarbeiten wiederkehrender Transaktionen: %v\n", err)
|
|
}
|
|
|
|
// Benutzer laden
|
|
var user models.User
|
|
if err := h.DB.First(&user, userID).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
data.UserName = user.Name
|
|
|
|
// Vermögensübersicht generieren
|
|
assetOverview, err := h.generateAssetOverview(userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
data.AssetOverview = *assetOverview
|
|
|
|
// Monatliche Statistiken generieren
|
|
monthlyStats, err := h.generateMonthlyStats(userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
data.MonthlyStats = monthlyStats
|
|
|
|
// Kategorie-Statistiken generieren
|
|
categoryStats, err := h.generateCategoryStats(userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
data.CategoryStats = categoryStats
|
|
|
|
// Transaktionstrends generieren
|
|
transactionTrend, err := h.generateTransactionTrend(userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
data.TransactionTrend = transactionTrend
|
|
|
|
// Aktuelle Transaktionen laden
|
|
recentTransactions, err := h.getRecentTransactions(userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
data.RecentTransactions = recentTransactions
|
|
|
|
// Wöchentliche Statistiken generieren
|
|
weeklyStats, err := h.generateWeeklyStats(userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
data.WeeklyStats = weeklyStats
|
|
|
|
return data, nil
|
|
}
|
|
|
|
// generateAssetOverview erstellt eine Vermögensübersicht
|
|
func (h *Handler) generateAssetOverview(userID uint) (*models.AssetOverview, error) {
|
|
overview := &models.AssetOverview{}
|
|
|
|
// Bank-Konten laden
|
|
var bankAccounts []models.BankAccount
|
|
if err := h.DB.Where("user_id = ?", userID).Find(&bankAccounts).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
totalBankBalance := 0.0
|
|
bankSummaries := make([]models.BankAccountSummary, len(bankAccounts))
|
|
for i, account := range bankAccounts {
|
|
totalBankBalance += account.Balance
|
|
bankSummaries[i] = models.BankAccountSummary{
|
|
ID: account.ID,
|
|
Name: account.Name,
|
|
Bank: account.Bank,
|
|
AccountType: account.AccountType,
|
|
Balance: account.Balance,
|
|
}
|
|
}
|
|
|
|
// Depots laden
|
|
var depots []models.Depot
|
|
if err := h.DB.Where("user_id = ?", userID).Find(&depots).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
totalDepotValue := 0.0
|
|
depotSummaries := make([]models.DepotSummary, len(depots))
|
|
for i, depot := range depots {
|
|
totalDepotValue += depot.TotalValue
|
|
depotSummaries[i] = models.DepotSummary{
|
|
ID: depot.ID,
|
|
Name: depot.Name,
|
|
Broker: depot.Broker,
|
|
TotalValue: depot.TotalValue,
|
|
}
|
|
}
|
|
|
|
overview.TotalBankBalance = totalBankBalance
|
|
overview.TotalDepotValue = totalDepotValue
|
|
overview.TotalAssets = totalBankBalance + totalDepotValue
|
|
overview.BankAccounts = bankSummaries
|
|
overview.Depots = depotSummaries
|
|
|
|
return overview, nil
|
|
}
|
|
|
|
// generateMonthlyStats erstellt monatliche Statistiken
|
|
func (h *Handler) generateMonthlyStats(userID uint) ([]models.MonthlyStats, error) {
|
|
var stats []models.MonthlyStats
|
|
|
|
// Letzte 12 Monate
|
|
now := time.Now()
|
|
for i := 11; i >= 0; i-- {
|
|
month := now.AddDate(0, -i, 0)
|
|
startOfMonth := time.Date(month.Year(), month.Month(), 1, 0, 0, 0, 0, month.Location())
|
|
endOfMonth := startOfMonth.AddDate(0, 1, 0).Add(-time.Nanosecond)
|
|
|
|
var income, expenses float64
|
|
|
|
// Einnahmen berechnen
|
|
h.DB.Model(&models.Transaction{}).
|
|
Where("user_id = ? AND type = ? AND date >= ? AND date <= ?", userID, "income", startOfMonth, endOfMonth).
|
|
Select("COALESCE(SUM(amount), 0)").Scan(&income)
|
|
|
|
// Ausgaben berechnen
|
|
h.DB.Model(&models.Transaction{}).
|
|
Where("user_id = ? AND type = ? AND date >= ? AND date <= ?", userID, "expense", startOfMonth, endOfMonth).
|
|
Select("COALESCE(SUM(amount), 0)").Scan(&expenses)
|
|
|
|
netChange := income - expenses
|
|
|
|
// Balance zu diesem Zeitpunkt (vereinfacht)
|
|
var balance float64
|
|
h.DB.Model(&models.Transaction{}).
|
|
Where("user_id = ? AND date <= ?", userID, endOfMonth).
|
|
Select("COALESCE(SUM(CASE WHEN type = 'income' THEN amount ELSE -amount END), 0)").Scan(&balance)
|
|
|
|
stats = append(stats, models.MonthlyStats{
|
|
Month: month.Format("2006-01"),
|
|
Income: income,
|
|
Expenses: expenses,
|
|
NetChange: netChange,
|
|
Balance: balance,
|
|
})
|
|
}
|
|
|
|
return stats, nil
|
|
}
|
|
|
|
// generateCategoryStats erstellt Kategorie-Statistiken
|
|
func (h *Handler) generateCategoryStats(userID uint) ([]models.CategoryStats, error) {
|
|
var stats []models.CategoryStats
|
|
|
|
// Letzte 90 Tage für bessere Relevanz
|
|
ninetyDaysAgo := time.Now().AddDate(0, 0, -90)
|
|
|
|
type categoryResult struct {
|
|
CategoryID *uint
|
|
CategoryName *string
|
|
TotalAmount float64
|
|
Count int64
|
|
}
|
|
|
|
var results []categoryResult
|
|
|
|
query := `
|
|
SELECT
|
|
t.category_id,
|
|
c.name as category_name,
|
|
SUM(t.amount) as total_amount,
|
|
COUNT(*) as count
|
|
FROM transactions t
|
|
LEFT JOIN categories c ON t.category_id = c.id
|
|
WHERE t.user_id = ? AND t.date >= ? AND t.type = 'expense'
|
|
GROUP BY t.category_id, c.name
|
|
ORDER BY total_amount DESC
|
|
`
|
|
|
|
if err := h.DB.Raw(query, userID, ninetyDaysAgo).Scan(&results).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Gesamtsumme für Prozentberechnung
|
|
var totalExpenses float64
|
|
for _, result := range results {
|
|
totalExpenses += result.TotalAmount
|
|
}
|
|
|
|
for _, result := range results {
|
|
categoryName := "Ohne Kategorie"
|
|
var categoryID uint = 0
|
|
|
|
if result.CategoryID != nil {
|
|
categoryID = *result.CategoryID
|
|
}
|
|
if result.CategoryName != nil {
|
|
categoryName = *result.CategoryName
|
|
}
|
|
|
|
percentage := 0.0
|
|
if totalExpenses > 0 {
|
|
percentage = (result.TotalAmount / totalExpenses) * 100
|
|
}
|
|
|
|
stats = append(stats, models.CategoryStats{
|
|
CategoryID: categoryID,
|
|
CategoryName: categoryName,
|
|
TotalAmount: result.TotalAmount,
|
|
Count: int(result.Count),
|
|
Percentage: percentage,
|
|
})
|
|
}
|
|
|
|
return stats, nil
|
|
}
|
|
|
|
// generateTransactionTrend erstellt Verlaufsdaten für Charts
|
|
func (h *Handler) generateTransactionTrend(userID uint) ([]models.TransactionTrend, error) {
|
|
var trends []models.TransactionTrend
|
|
|
|
// Nächste 12 Monate (für Trend mit wiederkehrenden Transaktionen)
|
|
now := time.Now()
|
|
|
|
// 12 Monate gruppiert nach Monaten
|
|
for i := 0; i < 12; i++ {
|
|
// Erster Tag des Monats
|
|
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()).AddDate(0, i, 0)
|
|
// Letzter Tag des Monats
|
|
monthEnd := monthStart.AddDate(0, 1, 0).Add(-time.Nanosecond)
|
|
|
|
var income, expense float64
|
|
|
|
// Einnahmen für diesen Monat (mit DATE-Funktion)
|
|
h.DB.Model(&models.Transaction{}).
|
|
Where("user_id = ? AND type = ? AND date >= ? AND date < ?", userID, "income", monthStart, monthEnd.AddDate(0, 0, 1)).
|
|
Select("COALESCE(SUM(amount), 0)").Scan(&income)
|
|
|
|
// Ausgaben für diesen Monat (mit DATE-Funktion)
|
|
h.DB.Model(&models.Transaction{}).
|
|
Where("user_id = ? AND type = ? AND date >= ? AND date < ?", userID, "expense", monthStart, monthEnd.AddDate(0, 0, 1)).
|
|
Select("COALESCE(SUM(amount), 0)").Scan(&expense)
|
|
|
|
// Aktueller Kontostand berechnen
|
|
var balance float64
|
|
if i == 0 {
|
|
// Für den ersten Monat: Aktueller Stand aus allen vergangenen Transaktionen
|
|
h.DB.Model(&models.Transaction{}).
|
|
Where("user_id = ? AND date <= ?", userID, monthEnd).
|
|
Select("COALESCE(SUM(CASE WHEN type = 'income' THEN amount ELSE -amount END), 0)").Scan(&balance)
|
|
} else {
|
|
// Für zukünftige Monate: Vorheriger Balance + heutige Einnahmen - heutige Ausgaben
|
|
if len(trends) > 0 {
|
|
balance = trends[len(trends)-1].Balance + income - expense
|
|
}
|
|
}
|
|
|
|
trends = append(trends, models.TransactionTrend{
|
|
Date: monthStart,
|
|
Income: income,
|
|
Expense: expense,
|
|
Balance: balance,
|
|
})
|
|
}
|
|
|
|
return trends, nil
|
|
}
|
|
|
|
// getRecentTransactions lädt die neuesten Transaktionen
|
|
func (h *Handler) getRecentTransactions(userID uint) ([]models.Transaction, error) {
|
|
var transactions []models.Transaction
|
|
|
|
err := h.DB.Where("user_id = ?", userID).
|
|
Preload("Category").
|
|
Preload("BankAccount").
|
|
Order("date DESC, created_at DESC").
|
|
Limit(10).
|
|
Find(&transactions).Error
|
|
|
|
return transactions, err
|
|
}
|
|
|
|
// generateWeeklyStats erstellt wöchentliche Statistiken
|
|
func (h *Handler) generateWeeklyStats(userID uint) ([]models.WeeklyStats, error) {
|
|
var stats []models.WeeklyStats
|
|
|
|
// Letzte 4 Wochen
|
|
now := time.Now()
|
|
for i := 3; i >= 0; i-- {
|
|
// Wochenstart (Montag)
|
|
weekStart := now.AddDate(0, 0, -7*i)
|
|
for weekStart.Weekday() != time.Monday {
|
|
weekStart = weekStart.AddDate(0, 0, -1)
|
|
}
|
|
weekStart = time.Date(weekStart.Year(), weekStart.Month(), weekStart.Day(), 0, 0, 0, 0, weekStart.Location())
|
|
weekEnd := weekStart.AddDate(0, 0, 6).Add(23*time.Hour + 59*time.Minute + 59*time.Second)
|
|
|
|
var income, expenses float64
|
|
|
|
// Einnahmen berechnen
|
|
h.DB.Model(&models.Transaction{}).
|
|
Where("user_id = ? AND type = ? AND date >= ? AND date <= ?", userID, "income", weekStart, weekEnd).
|
|
Select("COALESCE(SUM(amount), 0)").Scan(&income)
|
|
|
|
// Ausgaben berechnen
|
|
h.DB.Model(&models.Transaction{}).
|
|
Where("user_id = ? AND type = ? AND date >= ? AND date <= ?", userID, "expense", weekStart, weekEnd).
|
|
Select("COALESCE(SUM(amount), 0)").Scan(&expenses)
|
|
|
|
netChange := income - expenses
|
|
weekLabel := fmt.Sprintf("KW %d", getWeekNumber(weekStart))
|
|
|
|
stats = append(stats, models.WeeklyStats{
|
|
Week: weekLabel,
|
|
Income: income,
|
|
Expenses: expenses,
|
|
NetChange: netChange,
|
|
StartDate: weekStart,
|
|
EndDate: weekEnd,
|
|
})
|
|
}
|
|
|
|
return stats, nil
|
|
}
|
|
|
|
// getWeekNumber berechnet die Kalenderwoche
|
|
func getWeekNumber(date time.Time) int {
|
|
_, week := date.ISOWeek()
|
|
return week
|
|
}
|
|
|
|
// ShowLogin zeigt die Login-Seite
|
|
func (h *Handler) ShowLogin(c *gin.Context) {
|
|
errorMsg := ""
|
|
switch c.Query("error") {
|
|
case "invalid":
|
|
errorMsg = "Ungültige E-Mail-Adresse oder Passwort"
|
|
case "session":
|
|
errorMsg = "Fehler beim Erstellen der Sitzung. Bitte versuchen Sie es erneut."
|
|
case "required":
|
|
errorMsg = "Bitte melden Sie sich an, um diese Seite zu besuchen"
|
|
}
|
|
|
|
component := views.LoginPage(errorMsg)
|
|
component.Render(c.Request.Context(), c.Writer)
|
|
}
|
|
|
|
// ShowRegister zeigt die Registrierungs-Seite
|
|
func (h *Handler) ShowRegister(c *gin.Context) {
|
|
errorMsg := ""
|
|
switch c.Query("error") {
|
|
case "password_mismatch":
|
|
errorMsg = "Die Passwörter stimmen nicht überein"
|
|
case "email_exists":
|
|
errorMsg = "Ein Konto mit dieser E-Mail-Adresse existiert bereits"
|
|
case "server":
|
|
errorMsg = "Ein Server-Fehler ist aufgetreten. Bitte versuchen Sie es erneut."
|
|
case "session":
|
|
errorMsg = "Fehler beim Erstellen der Sitzung. Bitte versuchen Sie es erneut."
|
|
}
|
|
|
|
component := views.RegisterPage(errorMsg)
|
|
component.Render(c.Request.Context(), c.Writer)
|
|
}
|
|
|
|
// Login verarbeitet die Anmeldung
|
|
func (h *Handler) Login(c *gin.Context) {
|
|
email := c.PostForm("email")
|
|
password := c.PostForm("password")
|
|
|
|
// Benutzer in der Datenbank finden
|
|
var user models.User
|
|
if err := h.DB.Where("email = ?", email).First(&user).Error; err != nil {
|
|
c.Redirect(http.StatusSeeOther, "/login?error=invalid")
|
|
return
|
|
}
|
|
|
|
// Passwort überprüfen
|
|
if !user.CheckPassword(password) {
|
|
c.Redirect(http.StatusSeeOther, "/login?error=invalid")
|
|
return
|
|
}
|
|
|
|
// Session erstellen
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
session.Values["user_id"] = user.ID
|
|
session.Values["user_name"] = user.Name
|
|
|
|
// Session explizit speichern mit Fehlerbehandlung
|
|
if err := session.Save(c.Request, c.Writer); err != nil {
|
|
c.Redirect(http.StatusSeeOther, "/login?error=session")
|
|
return
|
|
}
|
|
|
|
c.Redirect(http.StatusSeeOther, "/")
|
|
}
|
|
|
|
// Register verarbeitet die Registrierung
|
|
func (h *Handler) Register(c *gin.Context) {
|
|
name := c.PostForm("name")
|
|
email := c.PostForm("email")
|
|
password := c.PostForm("password")
|
|
passwordConfirm := c.PostForm("password_confirm")
|
|
|
|
// Passwort-Bestätigung überprüfen
|
|
if password != passwordConfirm {
|
|
c.Redirect(http.StatusSeeOther, "/register?error=password_mismatch")
|
|
return
|
|
}
|
|
|
|
// Neuen Benutzer erstellen
|
|
user := models.User{
|
|
Name: name,
|
|
Email: email,
|
|
}
|
|
|
|
// Passwort hashen
|
|
if err := user.HashPassword(password); err != nil {
|
|
c.Redirect(http.StatusSeeOther, "/register?error=server")
|
|
return
|
|
}
|
|
|
|
// Benutzer in der Datenbank speichern
|
|
if err := h.DB.Create(&user).Error; err != nil {
|
|
c.Redirect(http.StatusSeeOther, "/register?error=email_exists")
|
|
return
|
|
}
|
|
|
|
// Session erstellen
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
session.Values["user_id"] = user.ID
|
|
session.Values["user_name"] = user.Name
|
|
|
|
// Session explizit speichern mit Fehlerbehandlung
|
|
if err := session.Save(c.Request, c.Writer); err != nil {
|
|
c.Redirect(http.StatusSeeOther, "/register?error=session")
|
|
return
|
|
}
|
|
|
|
c.Redirect(http.StatusSeeOther, "/")
|
|
}
|
|
|
|
// Logout meldet den Benutzer ab
|
|
func (h *Handler) Logout(c *gin.Context) {
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
session.Values["user_id"] = nil
|
|
session.Values["user_name"] = nil
|
|
session.Options.MaxAge = -1
|
|
session.Save(c.Request, c.Writer)
|
|
|
|
c.Redirect(http.StatusSeeOther, "/login")
|
|
}
|
|
|
|
// ShowAccounts zeigt die Konten- und Depot-Verwaltung
|
|
func (h *Handler) ShowAccounts(c *gin.Context) {
|
|
// Session überprüfen
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
userID, ok := session.Values["user_id"]
|
|
if !ok {
|
|
c.Redirect(http.StatusSeeOther, "/login?error=required")
|
|
return
|
|
}
|
|
|
|
userName, _ := session.Values["user_name"].(string)
|
|
|
|
// Bankkonten laden
|
|
var bankAccounts []models.BankAccount
|
|
h.DB.Where("user_id = ?", userID).Find(&bankAccounts)
|
|
|
|
// Depots laden
|
|
var depots []models.Depot
|
|
h.DB.Where("user_id = ?", userID).Find(&depots)
|
|
|
|
// Template rendern
|
|
views.Accounts(userName, bankAccounts, depots).Render(c.Request.Context(), c.Writer)
|
|
}
|
|
|
|
// CreateBankAccount erstellt ein neues Bankkonto
|
|
func (h *Handler) CreateBankAccount(c *gin.Context) {
|
|
// Session überprüfen
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
userID, ok := session.Values["user_id"]
|
|
if !ok {
|
|
c.Redirect(http.StatusSeeOther, "/login?error=required")
|
|
return
|
|
}
|
|
|
|
// Formulardaten lesen
|
|
name := c.PostForm("name")
|
|
bankName := c.PostForm("bank_name")
|
|
accountType := c.PostForm("account_type")
|
|
iban := c.PostForm("iban")
|
|
|
|
// Balance parsen
|
|
var balance float64
|
|
if balanceStr := c.PostForm("balance"); balanceStr != "" {
|
|
if parsed, err := parseFloat(balanceStr); err == nil {
|
|
balance = parsed
|
|
}
|
|
}
|
|
|
|
// Validierung
|
|
if name == "" || bankName == "" || accountType == "" {
|
|
c.Redirect(http.StatusSeeOther, "/accounts?error=required_fields")
|
|
return
|
|
}
|
|
|
|
// Bankkonto erstellen
|
|
bankAccount := models.BankAccount{
|
|
UserID: userID.(uint),
|
|
Name: name,
|
|
Bank: bankName,
|
|
AccountType: accountType,
|
|
IBAN: iban,
|
|
Balance: balance,
|
|
}
|
|
|
|
if err := h.DB.Create(&bankAccount).Error; err != nil {
|
|
c.Redirect(http.StatusSeeOther, "/accounts?error=create_failed")
|
|
return
|
|
}
|
|
|
|
c.Redirect(http.StatusSeeOther, "/accounts?success=bank_created")
|
|
}
|
|
|
|
// CreateDepot erstellt ein neues Depot
|
|
func (h *Handler) CreateDepot(c *gin.Context) {
|
|
// Session überprüfen
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
userID, ok := session.Values["user_id"]
|
|
if !ok {
|
|
c.Redirect(http.StatusSeeOther, "/login?error=required")
|
|
return
|
|
}
|
|
|
|
// Formulardaten lesen
|
|
name := c.PostForm("name")
|
|
brokerName := c.PostForm("broker_name")
|
|
depotNumber := c.PostForm("depot_number")
|
|
|
|
// Total Value parsen
|
|
var totalValue float64
|
|
if valueStr := c.PostForm("total_value"); valueStr != "" {
|
|
if parsed, err := parseFloat(valueStr); err == nil {
|
|
totalValue = parsed
|
|
}
|
|
}
|
|
|
|
// Validierung
|
|
if name == "" || brokerName == "" {
|
|
c.Redirect(http.StatusSeeOther, "/accounts?error=required_fields")
|
|
return
|
|
}
|
|
|
|
// Depot erstellen
|
|
depot := models.Depot{
|
|
UserID: userID.(uint),
|
|
Name: name,
|
|
Broker: brokerName,
|
|
DepotNumber: depotNumber,
|
|
TotalValue: totalValue,
|
|
}
|
|
|
|
if err := h.DB.Create(&depot).Error; err != nil {
|
|
c.Redirect(http.StatusSeeOther, "/accounts?error=create_failed")
|
|
return
|
|
}
|
|
|
|
c.Redirect(http.StatusSeeOther, "/accounts?success=depot_created")
|
|
}
|
|
|
|
// parseFloat ist eine Hilfsfunktion zum sicheren Parsen von Float-Werten
|
|
func parseFloat(s string) (float64, error) {
|
|
if s == "" {
|
|
return 0, nil
|
|
}
|
|
return strconv.ParseFloat(s, 64)
|
|
}
|
|
|
|
// ShowTransactions zeigt die Transaktionsübersicht
|
|
func (h *Handler) ShowTransactions(c *gin.Context) {
|
|
// Session überprüfen
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
userID, ok := session.Values["user_id"]
|
|
if !ok {
|
|
c.Redirect(http.StatusSeeOther, "/login?error=required")
|
|
return
|
|
}
|
|
|
|
userName, _ := session.Values["user_name"].(string)
|
|
|
|
// Paginierung Parameter
|
|
page := 1
|
|
if pageStr := c.Query("page"); pageStr != "" {
|
|
if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
|
|
page = p
|
|
}
|
|
}
|
|
|
|
pageSize := 20
|
|
offset := (page - 1) * pageSize
|
|
|
|
// Filter Parameter
|
|
dateFrom := c.Query("date_from")
|
|
dateTo := c.Query("date_to")
|
|
searchDescription := c.Query("search_description")
|
|
filterCategory := c.Query("filter_category")
|
|
filterAccount := c.Query("filter_account")
|
|
filterType := c.Query("filter_type")
|
|
amountMin := c.Query("amount_min")
|
|
amountMax := c.Query("amount_max")
|
|
|
|
// Base Query für Transaktionen
|
|
query := h.DB.Model(&models.Transaction{}).Where("user_id = ?", userID)
|
|
|
|
// Filter anwenden
|
|
if dateFrom != "" {
|
|
if parsedDate, err := time.Parse("2006-01-02", dateFrom); err == nil {
|
|
query = query.Where("date >= ?", parsedDate)
|
|
}
|
|
}
|
|
if dateTo != "" {
|
|
if parsedDate, err := time.Parse("2006-01-02", dateTo); err == nil {
|
|
query = query.Where("date <= ?", parsedDate)
|
|
}
|
|
}
|
|
if searchDescription != "" {
|
|
query = query.Where("description LIKE ?", "%"+searchDescription+"%")
|
|
}
|
|
if filterCategory != "" {
|
|
if filterCategory == "none" {
|
|
query = query.Where("category_id IS NULL")
|
|
} else if categoryID, err := strconv.Atoi(filterCategory); err == nil {
|
|
query = query.Where("category_id = ?", categoryID)
|
|
}
|
|
}
|
|
if filterAccount != "" {
|
|
if accountID, err := strconv.Atoi(filterAccount); err == nil {
|
|
query = query.Where("bank_account_id = ?", accountID)
|
|
}
|
|
}
|
|
if filterType != "" {
|
|
query = query.Where("type = ?", filterType)
|
|
}
|
|
if amountMin != "" {
|
|
if minAmount, err := strconv.ParseFloat(amountMin, 64); err == nil {
|
|
query = query.Where("amount >= ?", minAmount)
|
|
}
|
|
}
|
|
if amountMax != "" {
|
|
if maxAmount, err := strconv.ParseFloat(amountMax, 64); err == nil {
|
|
query = query.Where("amount <= ?", maxAmount)
|
|
}
|
|
}
|
|
|
|
// Gesamtanzahl der gefilterten Transaktionen für Paginierung
|
|
var totalCount int64
|
|
query.Count(&totalCount)
|
|
|
|
totalPages := int((totalCount + int64(pageSize) - 1) / int64(pageSize))
|
|
|
|
// Transaktionen laden (neueste zuerst, mit Paginierung und Filtern)
|
|
var transactions []models.Transaction
|
|
query.Preload("Category").
|
|
Preload("BankAccount").
|
|
Preload("RecurrenceRule").
|
|
Order("date DESC").
|
|
Limit(pageSize).
|
|
Offset(offset).
|
|
Find(&transactions)
|
|
|
|
// Bankkonten für Dropdown laden
|
|
var bankAccounts []models.BankAccount
|
|
h.DB.Where("user_id = ?", userID).Find(&bankAccounts)
|
|
|
|
// Kategorien laden
|
|
var categories []models.Category
|
|
h.DB.Where("user_id = ?", userID).Find(&categories)
|
|
|
|
// Regelmäßige Transaktionen laden
|
|
var recurrenceRules []models.RecurrenceRule
|
|
h.DB.Where("user_id = ? AND is_active = ?", userID, true).
|
|
Preload("Category").
|
|
Preload("BankAccount").
|
|
Find(&recurrenceRules)
|
|
|
|
// Template rendern
|
|
views.Transactions(userName, transactions, bankAccounts, categories, recurrenceRules, page, totalPages, totalCount).Render(c.Request.Context(), c.Writer)
|
|
}
|
|
|
|
// CreateTransaction erstellt eine neue Transaktion
|
|
func (h *Handler) CreateTransaction(c *gin.Context) {
|
|
// Session überprüfen
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
userID, ok := session.Values["user_id"]
|
|
if !ok {
|
|
c.Redirect(http.StatusSeeOther, "/login?error=required")
|
|
return
|
|
}
|
|
|
|
// Formulardaten lesen
|
|
description := c.PostForm("description")
|
|
amountStr := c.PostForm("amount")
|
|
transactionType := c.PostForm("type") // "income" or "expense"
|
|
dateStr := c.PostForm("date")
|
|
categoryIDStr := c.PostForm("category_id")
|
|
bankAccountIDStr := c.PostForm("bank_account_id")
|
|
|
|
// Validierung
|
|
if description == "" || amountStr == "" || transactionType == "" || dateStr == "" {
|
|
c.Redirect(http.StatusSeeOther, "/transactions?error=required_fields")
|
|
return
|
|
}
|
|
|
|
// Amount parsen
|
|
amount, err := strconv.ParseFloat(amountStr, 64)
|
|
if err != nil {
|
|
c.Redirect(http.StatusSeeOther, "/transactions?error=invalid_amount")
|
|
return
|
|
}
|
|
|
|
// Datum parsen
|
|
date, err := time.Parse("2006-01-02", dateStr)
|
|
if err != nil {
|
|
c.Redirect(http.StatusSeeOther, "/transactions?error=invalid_date")
|
|
return
|
|
}
|
|
|
|
// Optional: Category ID parsen
|
|
var categoryID *uint
|
|
if categoryIDStr != "" && categoryIDStr != "0" {
|
|
if parsed, err := strconv.ParseUint(categoryIDStr, 10, 32); err == nil {
|
|
id := uint(parsed)
|
|
categoryID = &id
|
|
}
|
|
}
|
|
|
|
// Optional: Bank Account ID parsen
|
|
var bankAccountID *uint
|
|
if bankAccountIDStr != "" && bankAccountIDStr != "0" {
|
|
if parsed, err := strconv.ParseUint(bankAccountIDStr, 10, 32); err == nil {
|
|
id := uint(parsed)
|
|
bankAccountID = &id
|
|
}
|
|
}
|
|
|
|
// Transaktion erstellen
|
|
transaction := models.Transaction{
|
|
UserID: userID.(uint),
|
|
Amount: amount,
|
|
Description: description,
|
|
Type: transactionType,
|
|
Date: date,
|
|
CategoryID: categoryID,
|
|
BankAccountID: bankAccountID,
|
|
IsRecurring: false,
|
|
}
|
|
|
|
if err := h.DB.Create(&transaction).Error; err != nil {
|
|
c.Redirect(http.StatusSeeOther, "/transactions?error=create_failed")
|
|
return
|
|
}
|
|
|
|
c.Redirect(http.StatusSeeOther, "/transactions?success=transaction_created")
|
|
}
|
|
|
|
// CreateRecurringTransaction erstellt eine neue regelmäßige Transaktion
|
|
func (h *Handler) CreateRecurringTransaction(c *gin.Context) {
|
|
// Session überprüfen
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
userID, ok := session.Values["user_id"]
|
|
if !ok {
|
|
c.Redirect(http.StatusSeeOther, "/login?error=required")
|
|
return
|
|
}
|
|
|
|
// Formulardaten lesen
|
|
description := c.PostForm("description")
|
|
amountStr := c.PostForm("amount")
|
|
transactionType := c.PostForm("type")
|
|
startDateStr := c.PostForm("start_date")
|
|
endDateStr := c.PostForm("end_date")
|
|
interval := c.PostForm("interval")
|
|
intervalCountStr := c.PostForm("interval_count")
|
|
categoryIDStr := c.PostForm("category_id")
|
|
bankAccountIDStr := c.PostForm("bank_account_id")
|
|
dayOfMonthStr := c.PostForm("day_of_month")
|
|
dayOfWeekStr := c.PostForm("day_of_week")
|
|
|
|
// Validierung
|
|
if description == "" || amountStr == "" || transactionType == "" || startDateStr == "" || interval == "" {
|
|
c.Redirect(http.StatusSeeOther, "/transactions?error=required_fields")
|
|
return
|
|
}
|
|
|
|
// Amount parsen
|
|
amount, err := strconv.ParseFloat(amountStr, 64)
|
|
if err != nil {
|
|
c.Redirect(http.StatusSeeOther, "/transactions?error=invalid_amount")
|
|
return
|
|
}
|
|
|
|
// Start-Datum parsen
|
|
startDate, err := time.Parse("2006-01-02", startDateStr)
|
|
if err != nil {
|
|
c.Redirect(http.StatusSeeOther, "/transactions?error=invalid_date")
|
|
return
|
|
}
|
|
|
|
// End-Datum parsen (optional)
|
|
var endDate *time.Time
|
|
if endDateStr != "" {
|
|
if parsed, err := time.Parse("2006-01-02", endDateStr); err == nil {
|
|
endDate = &parsed
|
|
}
|
|
}
|
|
|
|
// Interval Count parsen
|
|
intervalCount := 1
|
|
if intervalCountStr != "" {
|
|
if parsed, err := strconv.Atoi(intervalCountStr); err == nil && parsed > 0 {
|
|
intervalCount = parsed
|
|
}
|
|
}
|
|
|
|
// Optional: Category ID parsen
|
|
var categoryID *uint
|
|
if categoryIDStr != "" && categoryIDStr != "0" {
|
|
if parsed, err := strconv.ParseUint(categoryIDStr, 10, 32); err == nil {
|
|
id := uint(parsed)
|
|
categoryID = &id
|
|
}
|
|
}
|
|
|
|
// Optional: Bank Account ID parsen
|
|
var bankAccountID *uint
|
|
if bankAccountIDStr != "" && bankAccountIDStr != "0" {
|
|
if parsed, err := strconv.ParseUint(bankAccountIDStr, 10, 32); err == nil {
|
|
id := uint(parsed)
|
|
bankAccountID = &id
|
|
}
|
|
}
|
|
|
|
// Optional: Day of Month parsen
|
|
var dayOfMonth *int
|
|
if dayOfMonthStr != "" {
|
|
if parsed, err := strconv.Atoi(dayOfMonthStr); err == nil && parsed >= 1 && parsed <= 31 {
|
|
dayOfMonth = &parsed
|
|
}
|
|
}
|
|
|
|
// Optional: Day of Week parsen
|
|
var dayOfWeek *int
|
|
if dayOfWeekStr != "" {
|
|
if parsed, err := strconv.Atoi(dayOfWeekStr); err == nil && parsed >= 0 && parsed <= 6 {
|
|
dayOfWeek = &parsed
|
|
}
|
|
}
|
|
|
|
// RecurrenceRule erstellen
|
|
recurrenceRule := models.RecurrenceRule{
|
|
UserID: userID.(uint),
|
|
Amount: amount,
|
|
Description: description,
|
|
Type: transactionType,
|
|
CategoryID: categoryID,
|
|
BankAccountID: bankAccountID,
|
|
Interval: interval,
|
|
IntervalCount: intervalCount,
|
|
StartDate: startDate,
|
|
EndDate: endDate,
|
|
DayOfMonth: dayOfMonth,
|
|
DayOfWeek: dayOfWeek,
|
|
IsActive: true,
|
|
}
|
|
|
|
if err := h.DB.Create(&recurrenceRule).Error; err != nil {
|
|
c.Redirect(http.StatusSeeOther, "/transactions?error=create_failed")
|
|
return
|
|
}
|
|
|
|
c.Redirect(http.StatusSeeOther, "/transactions?success=recurring_created")
|
|
}
|
|
|
|
// DeleteTransaction löscht eine Transaktion
|
|
func (h *Handler) DeleteTransaction(c *gin.Context) {
|
|
// Session überprüfen
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
userID, ok := session.Values["user_id"]
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Nicht angemeldet"})
|
|
return
|
|
}
|
|
|
|
// Transaction ID aus URL Parameter
|
|
transactionIDStr := c.Param("id")
|
|
transactionID, err := strconv.ParseUint(transactionIDStr, 10, 32)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Ungültige Transaktions-ID"})
|
|
return
|
|
}
|
|
|
|
// Transaktion löschen (nur wenn sie dem Benutzer gehört)
|
|
result := h.DB.Where("id = ? AND user_id = ?", transactionID, userID).Delete(&models.Transaction{})
|
|
if result.Error != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Fehler beim Löschen"})
|
|
return
|
|
}
|
|
|
|
if result.RowsAffected == 0 {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Transaktion nicht gefunden"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"success": true})
|
|
}
|
|
|
|
// GetTransactionData gibt die Daten einer Transaktion für das Edit-Modal zurück
|
|
func (h *Handler) GetTransactionData(c *gin.Context) {
|
|
// Session überprüfen
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
userID, ok := session.Values["user_id"]
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Nicht angemeldet"})
|
|
return
|
|
}
|
|
|
|
// Transaction ID aus URL Parameter
|
|
transactionIDStr := c.Param("id")
|
|
transactionID, err := strconv.ParseUint(transactionIDStr, 10, 32)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Ungültige Transaktions-ID"})
|
|
return
|
|
}
|
|
|
|
// Transaktion laden
|
|
var transaction models.Transaction
|
|
result := h.DB.Where("id = ? AND user_id = ?", transactionID, userID).First(&transaction)
|
|
if result.Error != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Transaktion nicht gefunden"})
|
|
return
|
|
}
|
|
|
|
// Daten für JavaScript formatieren
|
|
transactionData := gin.H{
|
|
"id": transaction.ID,
|
|
"description": transaction.Description,
|
|
"amount": transaction.Amount,
|
|
"type": transaction.Type,
|
|
"date": transaction.Date.Format("2006-01-02"),
|
|
"category_id": transaction.CategoryID,
|
|
"bank_account_id": transaction.BankAccountID,
|
|
}
|
|
|
|
c.JSON(http.StatusOK, transactionData)
|
|
}
|
|
|
|
// UpdateTransaction aktualisiert eine existierende Transaktion
|
|
func (h *Handler) UpdateTransaction(c *gin.Context) {
|
|
// Session überprüfen
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
userID, ok := session.Values["user_id"]
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Nicht angemeldet"})
|
|
return
|
|
}
|
|
|
|
// Transaction ID aus URL Parameter
|
|
transactionIDStr := c.Param("id")
|
|
transactionID, err := strconv.ParseUint(transactionIDStr, 10, 32)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Ungültige Transaktions-ID"})
|
|
return
|
|
}
|
|
|
|
// JSON Body parsen
|
|
var reqData struct {
|
|
Description string `json:"description"`
|
|
Amount string `json:"amount"`
|
|
Type string `json:"type"`
|
|
Date string `json:"date"`
|
|
CategoryID string `json:"category_id"`
|
|
BankAccountID string `json:"bank_account_id"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&reqData); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Ungültige Daten"})
|
|
return
|
|
}
|
|
|
|
// Daten validieren und konvertieren
|
|
amount, err := strconv.ParseFloat(reqData.Amount, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Ungültiger Betrag"})
|
|
return
|
|
}
|
|
|
|
date, err := time.Parse("2006-01-02", reqData.Date)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Ungültiges Datum"})
|
|
return
|
|
}
|
|
|
|
bankAccountID, err := strconv.ParseUint(reqData.BankAccountID, 10, 32)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Ungültige Konto-ID"})
|
|
return
|
|
}
|
|
|
|
// Optional: Category ID parsen
|
|
var categoryID *uint
|
|
if reqData.CategoryID != "" && reqData.CategoryID != "0" {
|
|
if parsed, err := strconv.ParseUint(reqData.CategoryID, 10, 32); err == nil {
|
|
id := uint(parsed)
|
|
categoryID = &id
|
|
}
|
|
}
|
|
|
|
// Transaktion laden und aktualisieren
|
|
var transaction models.Transaction
|
|
result := h.DB.Where("id = ? AND user_id = ?", transactionID, userID).First(&transaction)
|
|
if result.Error != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Transaktion nicht gefunden"})
|
|
return
|
|
}
|
|
|
|
// Transaktion aktualisieren
|
|
transaction.Description = reqData.Description
|
|
transaction.Amount = amount
|
|
transaction.Type = reqData.Type
|
|
transaction.Date = date
|
|
transaction.CategoryID = categoryID
|
|
bankID := uint(bankAccountID)
|
|
transaction.BankAccountID = &bankID
|
|
|
|
if err := h.DB.Save(&transaction).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Fehler beim Speichern"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"success": true})
|
|
}
|
|
|
|
// MultiUpdateTransactions aktualisiert mehrere Transaktionen gleichzeitig
|
|
func (h *Handler) MultiUpdateTransactions(c *gin.Context) {
|
|
// Session überprüfen
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
userID, ok := session.Values["user_id"]
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Nicht angemeldet"})
|
|
return
|
|
}
|
|
|
|
// JSON Request parsen
|
|
var reqData struct {
|
|
TransactionIDs []string `json:"transaction_ids"`
|
|
Description *string `json:"description,omitempty"`
|
|
Amount *string `json:"amount,omitempty"`
|
|
Type *string `json:"type,omitempty"`
|
|
Date *string `json:"date,omitempty"`
|
|
CategoryID *string `json:"category_id,omitempty"`
|
|
BankAccountID *string `json:"bank_account_id,omitempty"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&reqData); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Ungültige Anfrage"})
|
|
return
|
|
}
|
|
|
|
if len(reqData.TransactionIDs) == 0 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Keine Transaktionen ausgewählt"})
|
|
return
|
|
}
|
|
|
|
// Updates vorbereiten
|
|
updates := make(map[string]interface{})
|
|
|
|
if reqData.Description != nil && *reqData.Description != "" {
|
|
updates["description"] = *reqData.Description
|
|
}
|
|
|
|
if reqData.Amount != nil && *reqData.Amount != "" {
|
|
amount, err := strconv.ParseFloat(*reqData.Amount, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Ungültiger Betrag"})
|
|
return
|
|
}
|
|
updates["amount"] = amount
|
|
}
|
|
|
|
if reqData.Type != nil && *reqData.Type != "" {
|
|
if *reqData.Type != "income" && *reqData.Type != "expense" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Ungültiger Transaktionstyp"})
|
|
return
|
|
}
|
|
updates["type"] = *reqData.Type
|
|
}
|
|
|
|
if reqData.Date != nil && *reqData.Date != "" {
|
|
date, err := time.Parse("2006-01-02", *reqData.Date)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Ungültiges Datum"})
|
|
return
|
|
}
|
|
updates["date"] = date
|
|
}
|
|
|
|
if reqData.CategoryID != nil {
|
|
if *reqData.CategoryID == "null" || *reqData.CategoryID == "" {
|
|
updates["category_id"] = nil
|
|
} else {
|
|
categoryID, err := strconv.ParseUint(*reqData.CategoryID, 10, 32)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Ungültige Kategorie-ID"})
|
|
return
|
|
}
|
|
updates["category_id"] = uint(categoryID)
|
|
}
|
|
}
|
|
|
|
if reqData.BankAccountID != nil {
|
|
if *reqData.BankAccountID == "null" || *reqData.BankAccountID == "" {
|
|
updates["bank_account_id"] = nil
|
|
} else {
|
|
bankAccountID, err := strconv.ParseUint(*reqData.BankAccountID, 10, 32)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Ungültige Konto-ID"})
|
|
return
|
|
}
|
|
bankID := uint(bankAccountID)
|
|
updates["bank_account_id"] = &bankID
|
|
}
|
|
}
|
|
|
|
if len(updates) == 0 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Keine Änderungen angegeben"})
|
|
return
|
|
}
|
|
|
|
// Transaktionen aktualisieren
|
|
result := h.DB.Model(&models.Transaction{}).
|
|
Where("id IN ? AND user_id = ?", reqData.TransactionIDs, userID).
|
|
Updates(updates)
|
|
|
|
if result.Error != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Fehler beim Aktualisieren der Transaktionen"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"updated": result.RowsAffected,
|
|
})
|
|
}
|
|
|
|
// MultiDeleteTransactions löscht mehrere Transaktionen gleichzeitig
|
|
func (h *Handler) MultiDeleteTransactions(c *gin.Context) {
|
|
// Session überprüfen
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
userID, ok := session.Values["user_id"]
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Nicht angemeldet"})
|
|
return
|
|
}
|
|
|
|
// JSON Request parsen
|
|
var reqData struct {
|
|
TransactionIDs []string `json:"transaction_ids"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&reqData); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Ungültige Anfrage"})
|
|
return
|
|
}
|
|
|
|
if len(reqData.TransactionIDs) == 0 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Keine Transaktionen ausgewählt"})
|
|
return
|
|
}
|
|
|
|
// Transaktionen löschen (nur die des angemeldeten Users)
|
|
result := h.DB.Where("id IN ? AND user_id = ?", reqData.TransactionIDs, userID).Delete(&models.Transaction{})
|
|
|
|
if result.Error != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Fehler beim Löschen der Transaktionen"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"deleted": result.RowsAffected,
|
|
})
|
|
}
|
|
|
|
// ToggleRecurrenceRule aktiviert/deaktiviert eine regelmäßige Transaktion
|
|
func (h *Handler) ToggleRecurrenceRule(c *gin.Context) {
|
|
// Session überprüfen
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
userID, ok := session.Values["user_id"]
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Nicht angemeldet"})
|
|
return
|
|
}
|
|
|
|
// RecurrenceRule ID aus URL Parameter
|
|
ruleIDStr := c.Param("id")
|
|
ruleID, err := strconv.ParseUint(ruleIDStr, 10, 32)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Ungültige Regel-ID"})
|
|
return
|
|
}
|
|
|
|
// Regel finden und Status umschalten
|
|
var rule models.RecurrenceRule
|
|
if err := h.DB.Where("id = ? AND user_id = ?", ruleID, userID).First(&rule).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Regel nicht gefunden"})
|
|
return
|
|
}
|
|
|
|
rule.IsActive = !rule.IsActive
|
|
if err := h.DB.Save(&rule).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Fehler beim Speichern"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"success": true, "is_active": rule.IsActive})
|
|
}
|
|
|
|
// generateTestData erstellt Testdaten für ein leeres Dashboard
|
|
func (h *Handler) generateTestData(userID uint) error {
|
|
// Prüfen, ob bereits Transaktionen vorhanden sind
|
|
var count int64
|
|
h.DB.Model(&models.Transaction{}).Where("user_id = ?", userID).Count(&count)
|
|
if count > 0 {
|
|
return nil // Bereits Daten vorhanden
|
|
}
|
|
|
|
// Prüfen, ob bereits wiederkehrende Regeln vorhanden sind
|
|
var ruleCount int64
|
|
h.DB.Model(&models.RecurrenceRule{}).Where("user_id = ?", userID).Count(&ruleCount)
|
|
if ruleCount > 0 {
|
|
return nil // Bereits Regeln vorhanden
|
|
}
|
|
|
|
// Beispielkategorien erstellen
|
|
categories := []models.Category{
|
|
{UserID: userID, Name: "Gehalt", Description: "Monatliches Gehalt", Color: "#22c55e", Icon: "💰"},
|
|
{UserID: userID, Name: "Lebensmittel", Description: "Einkäufe und Groceries", Color: "#f59e0b", Icon: "🛒"},
|
|
{UserID: userID, Name: "Transport", Description: "ÖPNV, Benzin, etc.", Color: "#3b82f6", Icon: "🚗"},
|
|
{UserID: userID, Name: "Unterhaltung", Description: "Kino, Restaurant, etc.", Color: "#8b5cf6", Icon: "🎬"},
|
|
{UserID: userID, Name: "Miete", Description: "Wohnungsmiete", Color: "#ef4444", Icon: "🏠"},
|
|
}
|
|
|
|
for _, category := range categories {
|
|
if err := h.DB.Create(&category).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Testdaten für die letzten 3 Monate generieren
|
|
now := time.Now()
|
|
transactions := []models.Transaction{}
|
|
|
|
for i := 0; i < 90; i++ { // 90 Tage
|
|
date := now.AddDate(0, 0, -i)
|
|
|
|
// Gehalt am ersten des Monats
|
|
if date.Day() == 1 {
|
|
transactions = append(transactions, models.Transaction{
|
|
UserID: userID,
|
|
Amount: 3500.0,
|
|
Description: "Gehalt",
|
|
Type: "income",
|
|
Date: date,
|
|
CategoryID: &categories[0].ID,
|
|
})
|
|
}
|
|
|
|
// Miete am 1. des Monats
|
|
if date.Day() == 1 {
|
|
transactions = append(transactions, models.Transaction{
|
|
UserID: userID,
|
|
Amount: 1200.0,
|
|
Description: "Wohnungsmiete",
|
|
Type: "expense",
|
|
Date: date,
|
|
CategoryID: &categories[4].ID,
|
|
})
|
|
}
|
|
|
|
// Zufällige tägliche Ausgaben
|
|
if i%3 == 0 { // Jeden 3. Tag
|
|
// Lebensmittel
|
|
transactions = append(transactions, models.Transaction{
|
|
UserID: userID,
|
|
Amount: float64(15 + (i%50)),
|
|
Description: "Einkauf Supermarkt",
|
|
Type: "expense",
|
|
Date: date,
|
|
CategoryID: &categories[1].ID,
|
|
})
|
|
}
|
|
|
|
if i%7 == 0 { // Wöchentlich
|
|
// Transport
|
|
transactions = append(transactions, models.Transaction{
|
|
UserID: userID,
|
|
Amount: float64(25 + (i%30)),
|
|
Description: "ÖPNV Ticket",
|
|
Type: "expense",
|
|
Date: date,
|
|
CategoryID: &categories[2].ID,
|
|
})
|
|
}
|
|
|
|
if i%10 == 0 { // Alle 10 Tage
|
|
// Unterhaltung
|
|
transactions = append(transactions, models.Transaction{
|
|
UserID: userID,
|
|
Amount: float64(40 + (i%60)),
|
|
Description: "Restaurant / Kino",
|
|
Type: "expense",
|
|
Date: date,
|
|
CategoryID: &categories[3].ID,
|
|
})
|
|
}
|
|
}
|
|
|
|
// Alle Transaktionen in die Datenbank speichern
|
|
for _, transaction := range transactions {
|
|
if err := h.DB.Create(&transaction).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Wiederkehrende Transaktionen erstellen
|
|
rules := []models.RecurrenceRule{
|
|
{
|
|
UserID: userID,
|
|
Amount: 3500.0,
|
|
Description: "Monatliches Gehalt",
|
|
CategoryID: &categories[0].ID,
|
|
Type: "income",
|
|
Interval: "monthly",
|
|
IntervalCount: 1,
|
|
StartDate: time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()),
|
|
DayOfMonth: intPtr(1),
|
|
IsActive: true,
|
|
},
|
|
{
|
|
UserID: userID,
|
|
Amount: 1200.0,
|
|
Description: "Wohnungsmiete",
|
|
CategoryID: &categories[4].ID,
|
|
Type: "expense",
|
|
Interval: "monthly",
|
|
IntervalCount: 1,
|
|
StartDate: time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()),
|
|
DayOfMonth: intPtr(1),
|
|
IsActive: true,
|
|
},
|
|
{
|
|
UserID: userID,
|
|
Amount: 80.0,
|
|
Description: "Wöchentlicher Einkauf",
|
|
CategoryID: &categories[1].ID,
|
|
Type: "expense",
|
|
Interval: "weekly",
|
|
IntervalCount: 1,
|
|
StartDate: time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()),
|
|
DayOfWeek: intPtr(6), // Samstag
|
|
IsActive: true,
|
|
},
|
|
}
|
|
|
|
// Wiederkehrende Regeln speichern
|
|
for _, rule := range rules {
|
|
if err := h.DB.Create(&rule).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// intPtr gibt einen Pointer auf einen int zurück
|
|
func intPtr(i int) *int {
|
|
return &i
|
|
}
|
|
|
|
// TestRecurringTransactions ist eine Test-Route für wiederkehrende Transaktionen
|
|
func (h *Handler) TestRecurringTransactions(c *gin.Context) {
|
|
// Session überprüfen
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
userID, ok := session.Values["user_id"]
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Nicht angemeldet"})
|
|
return
|
|
}
|
|
|
|
// Wiederkehrende Transaktionen verarbeiten
|
|
err := h.processRecurringTransactions(userID.(uint))
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Aktuelle Transaktionen abfragen
|
|
var transactions []models.Transaction
|
|
h.DB.Where("user_id = ? AND is_recurring = ?", userID, true).
|
|
Preload("Category").
|
|
Order("date DESC").
|
|
Limit(20).
|
|
Find(&transactions)
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "Wiederkehrende Transaktionen verarbeitet",
|
|
"recurring_transactions": transactions,
|
|
})
|
|
}
|
|
|
|
// UpdateRecurrenceRule aktualisiert eine wiederkehrende Transaktion
|
|
func (h *Handler) UpdateRecurrenceRule(c *gin.Context) {
|
|
// Session überprüfen
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
userID, ok := session.Values["user_id"]
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Nicht angemeldet"})
|
|
return
|
|
}
|
|
|
|
// RecurrenceRule ID aus URL Parameter
|
|
ruleIDStr := c.Param("id")
|
|
ruleID, err := strconv.ParseUint(ruleIDStr, 10, 32)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Ungültige Regel-ID"})
|
|
return
|
|
}
|
|
|
|
// Existierende Regel laden
|
|
var rule models.RecurrenceRule
|
|
if err := h.DB.Where("id = ? AND user_id = ?", ruleID, userID).First(&rule).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Regel nicht gefunden"})
|
|
return
|
|
}
|
|
|
|
// Request Body parsen
|
|
var updateData struct {
|
|
Amount *float64 `json:"amount"`
|
|
Description *string `json:"description"`
|
|
CategoryID *uint `json:"category_id"`
|
|
Type *string `json:"type"`
|
|
Interval *string `json:"interval"`
|
|
IntervalCount *int `json:"interval_count"`
|
|
DayOfWeek *int `json:"day_of_week"`
|
|
DayOfMonth *int `json:"day_of_month"`
|
|
IsActive *bool `json:"is_active"`
|
|
StartDate *string `json:"start_date"`
|
|
EndDate *string `json:"end_date"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&updateData); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Ungültige Daten"})
|
|
return
|
|
}
|
|
|
|
// Felder aktualisieren (nur wenn sie gesetzt sind)
|
|
if updateData.Amount != nil {
|
|
rule.Amount = *updateData.Amount
|
|
}
|
|
if updateData.Description != nil {
|
|
rule.Description = *updateData.Description
|
|
}
|
|
if updateData.CategoryID != nil {
|
|
rule.CategoryID = updateData.CategoryID
|
|
}
|
|
if updateData.Type != nil {
|
|
rule.Type = *updateData.Type
|
|
}
|
|
if updateData.Interval != nil {
|
|
rule.Interval = *updateData.Interval
|
|
}
|
|
if updateData.IntervalCount != nil {
|
|
rule.IntervalCount = *updateData.IntervalCount
|
|
}
|
|
if updateData.DayOfWeek != nil {
|
|
rule.DayOfWeek = updateData.DayOfWeek
|
|
}
|
|
if updateData.DayOfMonth != nil {
|
|
rule.DayOfMonth = updateData.DayOfMonth
|
|
}
|
|
if updateData.IsActive != nil {
|
|
rule.IsActive = *updateData.IsActive
|
|
}
|
|
if updateData.StartDate != nil {
|
|
if startDate, err := time.Parse("2006-01-02", *updateData.StartDate); err == nil {
|
|
rule.StartDate = startDate
|
|
}
|
|
}
|
|
if updateData.EndDate != nil {
|
|
if *updateData.EndDate == "" {
|
|
rule.EndDate = nil
|
|
} else {
|
|
if endDate, err := time.Parse("2006-01-02", *updateData.EndDate); err == nil {
|
|
rule.EndDate = &endDate
|
|
}
|
|
}
|
|
}
|
|
|
|
// Regel speichern
|
|
if err := h.DB.Save(&rule).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Fehler beim Speichern"})
|
|
return
|
|
}
|
|
|
|
// Aktualisierte Regel mit Kategorie laden
|
|
h.DB.Preload("Category").First(&rule, rule.ID)
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"rule": rule,
|
|
})
|
|
}
|
|
|
|
// GetRecurrenceRules lädt alle wiederkehrenden Transaktionen eines Benutzers
|
|
func (h *Handler) GetRecurrenceRules(c *gin.Context) {
|
|
// Session überprüfen
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
userID, ok := session.Values["user_id"]
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Nicht angemeldet"})
|
|
return
|
|
}
|
|
|
|
// Alle Regeln des Benutzers laden
|
|
var rules []models.RecurrenceRule
|
|
if err := h.DB.Where("user_id = ?", userID).
|
|
Preload("Category").
|
|
Order("created_at DESC").
|
|
Find(&rules).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Fehler beim Laden der Regeln"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"rules": rules,
|
|
})
|
|
}
|
|
|
|
// DeleteRecurrenceRule löscht eine wiederkehrende Transaktion
|
|
func (h *Handler) DeleteRecurrenceRule(c *gin.Context) {
|
|
// Session überprüfen
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
userID, ok := session.Values["user_id"]
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Nicht angemeldet"})
|
|
return
|
|
}
|
|
|
|
// RecurrenceRule ID aus URL Parameter
|
|
ruleIDStr := c.Param("id")
|
|
ruleID, err := strconv.ParseUint(ruleIDStr, 10, 32)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Ungültige Regel-ID"})
|
|
return
|
|
}
|
|
|
|
// Regel löschen (nur wenn sie dem Benutzer gehört)
|
|
result := h.DB.Where("id = ? AND user_id = ?", ruleID, userID).Delete(&models.RecurrenceRule{})
|
|
if result.Error != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Fehler beim Löschen"})
|
|
return
|
|
}
|
|
|
|
if result.RowsAffected == 0 {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Regel nicht gefunden"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"success": true})
|
|
}
|
|
|
|
// processRecurringTransactions generiert alle fälligen wiederkehrenden Transaktionen
|
|
func (h *Handler) processRecurringTransactions(userID uint) error {
|
|
// Alle aktiven Wiederholungsregeln für den Benutzer laden
|
|
var rules []models.RecurrenceRule
|
|
if err := h.DB.Where("user_id = ? AND is_active = ?", userID, true).Find(&rules).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
// Für jede Regel prüfen, ob neue Transaktionen generiert werden müssen
|
|
for _, rule := range rules {
|
|
// Bis zu 10 Transaktionen pro Regel generieren (um Endlosschleifen zu vermeiden)
|
|
for i := 0; i < 10; i++ {
|
|
transaction, err := rule.GenerateTransaction(h.DB)
|
|
if err != nil {
|
|
// Fehler loggen, aber weitermachen
|
|
fmt.Printf("Fehler beim Generieren einer wiederkehrenden Transaktion (Regel ID %d): %v\n", rule.ID, err)
|
|
break
|
|
}
|
|
|
|
if transaction == nil {
|
|
// Keine weitere Transaktion zu generieren
|
|
break
|
|
}
|
|
|
|
fmt.Printf("Wiederkehrende Transaktion generiert: %s (€%.2f) für %s\n",
|
|
transaction.Description, transaction.Amount, transaction.Date.Format("02.01.2006"))
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ShowRecurringTransactions zeigt die Verwaltungsseite für wiederkehrende Transaktionen
|
|
func (h *Handler) ShowRecurringTransactions(c *gin.Context) {
|
|
// Session überprüfen
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
userID, ok := session.Values["user_id"]
|
|
if !ok {
|
|
c.Redirect(http.StatusSeeOther, "/login?error=required")
|
|
return
|
|
}
|
|
|
|
// Benutzer aus der Datenbank laden
|
|
var user models.User
|
|
if err := h.DB.First(&user, userID).Error; err != nil {
|
|
c.Redirect(http.StatusSeeOther, "/login?error=user_not_found")
|
|
return
|
|
}
|
|
|
|
// Alle Regeln des Benutzers laden
|
|
var rules []models.RecurrenceRule
|
|
if err := h.DB.Where("user_id = ?", userID).
|
|
Preload("Category").
|
|
Order("created_at DESC").
|
|
Find(&rules).Error; err != nil {
|
|
c.String(http.StatusInternalServerError, "Fehler beim Laden der wiederkehrenden Transaktionen")
|
|
return
|
|
}
|
|
|
|
// Template rendern
|
|
component := views.RecurringTransactions(user.Name, rules)
|
|
component.Render(c.Request.Context(), c.Writer)
|
|
}
|
|
|
|
// ShowSettings zeigt die Einstellungsseite an
|
|
func (h *Handler) ShowSettings(c *gin.Context) {
|
|
// Session überprüfen
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
userID, ok := session.Values["user_id"]
|
|
if !ok {
|
|
c.Redirect(http.StatusSeeOther, "/login")
|
|
return
|
|
}
|
|
|
|
// Benutzer laden
|
|
var user models.User
|
|
if err := h.DB.First(&user, userID).Error; err != nil {
|
|
c.String(http.StatusInternalServerError, "Fehler beim Laden der Benutzerdaten")
|
|
return
|
|
}
|
|
|
|
// Kategorien laden
|
|
var categories []models.Category
|
|
h.DB.Where("user_id = ?", userID).Find(&categories)
|
|
|
|
// Bankkonten laden
|
|
var bankAccounts []models.BankAccount
|
|
h.DB.Where("user_id = ?", userID).Find(&bankAccounts)
|
|
|
|
// Template rendern
|
|
views.Settings(user.Name, user, categories, bankAccounts).Render(c.Request.Context(), c.Writer)
|
|
}
|
|
|
|
// UpdateUserSettings aktualisiert die Benutzereinstellungen
|
|
func (h *Handler) UpdateUserSettings(c *gin.Context) {
|
|
// Session überprüfen
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
userID, ok := session.Values["user_id"]
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Nicht angemeldet"})
|
|
return
|
|
}
|
|
|
|
// JSON Body parsen
|
|
var reqData struct {
|
|
Username string `json:"username"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&reqData); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Ungültige Daten"})
|
|
return
|
|
}
|
|
|
|
// Benutzer aktualisieren (nur Name, E-Mail bleibt unverändert)
|
|
result := h.DB.Model(&models.User{}).Where("id = ?", userID).Update("name", reqData.Username)
|
|
|
|
if result.Error != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Fehler beim Speichern"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"success": true})
|
|
}
|
|
|
|
// UpdatePassword ändert das Benutzerpasswort
|
|
func (h *Handler) UpdatePassword(c *gin.Context) {
|
|
// Session überprüfen
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
userID, ok := session.Values["user_id"]
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Nicht angemeldet"})
|
|
return
|
|
}
|
|
|
|
// JSON Body parsen
|
|
var reqData struct {
|
|
CurrentPassword string `json:"current_password"`
|
|
NewPassword string `json:"new_password"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&reqData); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Ungültige Daten"})
|
|
return
|
|
}
|
|
|
|
// Benutzer laden
|
|
var user models.User
|
|
if err := h.DB.First(&user, userID).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Fehler beim Laden der Benutzerdaten"})
|
|
return
|
|
}
|
|
|
|
// Aktuelles Passwort überprüfen
|
|
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(reqData.CurrentPassword)); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Aktuelles Passwort ist falsch"})
|
|
return
|
|
}
|
|
|
|
// Neues Passwort hashen
|
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(reqData.NewPassword), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Fehler beim Hashen des Passworts"})
|
|
return
|
|
}
|
|
|
|
// Passwort aktualisieren
|
|
result := h.DB.Model(&user).Update("password_hash", string(hashedPassword))
|
|
if result.Error != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Fehler beim Speichern des Passworts"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"success": true})
|
|
}
|
|
|
|
// CreateCategory erstellt eine neue Kategorie
|
|
func (h *Handler) CreateCategory(c *gin.Context) {
|
|
// Session überprüfen
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
userID, ok := session.Values["user_id"]
|
|
if !ok {
|
|
c.Redirect(http.StatusSeeOther, "/login")
|
|
return
|
|
}
|
|
|
|
// Form-Daten lesen
|
|
name := c.PostForm("name")
|
|
icon := c.PostForm("icon")
|
|
color := c.PostForm("color")
|
|
|
|
if name == "" || icon == "" {
|
|
c.Redirect(http.StatusSeeOther, "/settings?error=invalid_data")
|
|
return
|
|
}
|
|
|
|
// Kategorie erstellen
|
|
category := models.Category{
|
|
Name: name,
|
|
Icon: icon,
|
|
Color: color,
|
|
UserID: userID.(uint),
|
|
}
|
|
|
|
if err := h.DB.Create(&category).Error; err != nil {
|
|
c.Redirect(http.StatusSeeOther, "/settings?error=create_failed")
|
|
return
|
|
}
|
|
|
|
c.Redirect(http.StatusSeeOther, "/settings")
|
|
}
|
|
|
|
// DeleteCategory löscht eine Kategorie
|
|
func (h *Handler) DeleteCategory(c *gin.Context) {
|
|
// Session überprüfen
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
userID, ok := session.Values["user_id"]
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Nicht angemeldet"})
|
|
return
|
|
}
|
|
|
|
// Category ID aus URL Parameter
|
|
categoryIDStr := c.Param("id")
|
|
categoryID, err := strconv.ParseUint(categoryIDStr, 10, 32)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Ungültige Kategorie-ID"})
|
|
return
|
|
}
|
|
|
|
// Kategorie löschen (nur wenn sie dem Benutzer gehört)
|
|
result := h.DB.Where("id = ? AND user_id = ?", categoryID, userID).Delete(&models.Category{})
|
|
if result.Error != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Fehler beim Löschen"})
|
|
return
|
|
}
|
|
|
|
if result.RowsAffected == 0 {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Kategorie nicht gefunden"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"success": true})
|
|
}
|
|
|
|
// DeleteBankAccount löscht ein Bankkonto
|
|
func (h *Handler) DeleteBankAccount(c *gin.Context) {
|
|
// Session überprüfen
|
|
session, _ := h.Store.Get(c.Request, "user-session")
|
|
userID, ok := session.Values["user_id"]
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Nicht angemeldet"})
|
|
return
|
|
}
|
|
|
|
// Account ID aus URL Parameter
|
|
accountIDStr := c.Param("id")
|
|
accountID, err := strconv.ParseUint(accountIDStr, 10, 32)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Ungültige Konto-ID"})
|
|
return
|
|
}
|
|
|
|
// Konto löschen (nur wenn es dem Benutzer gehört)
|
|
result := h.DB.Where("id = ? AND user_id = ?", accountID, userID).Delete(&models.BankAccount{})
|
|
if result.Error != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Fehler beim Löschen"})
|
|
return
|
|
}
|
|
|
|
if result.RowsAffected == 0 {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Konto nicht gefunden"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"success": true})
|
|
}
|