diff --git a/Screenshot 2026-03-04 100331.ico b/Screenshot 2026-03-04 100331.ico new file mode 100644 index 0000000..8551563 Binary files /dev/null and b/Screenshot 2026-03-04 100331.ico differ diff --git a/SkyTeam/AdminDashBoard.xaml.cs b/SkyTeam/AdminDashBoard.xaml.cs index d48b537..dfe7a67 100644 --- a/SkyTeam/AdminDashBoard.xaml.cs +++ b/SkyTeam/AdminDashBoard.xaml.cs @@ -11,6 +11,7 @@ namespace SkyTeam { public AdminDashboard() { + // Quelle: Im Unterricht gemacht InitializeComponent(); LoadUsers(); LoadFlights(); @@ -19,12 +20,15 @@ namespace SkyTeam private void LoadUsers() { + // Quelle: Im Unterricht gemacht BindGrid("SELECT Id, Vorname, Nachname, Email, Rolle FROM users", AllUsersGrid); } private void AllUsersGrid_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (AllUsersGrid.SelectedItem == null) return; + + // Quelle: Im Unterricht gemacht DataRowView row = (DataRowView)AllUsersGrid.SelectedItem; int userId = Convert.ToInt32(row["Id"]); BindGrid($"SELECT b.Id AS BuchungId, f.Flugnummer, f.Abflugort, f.Zielort, f.Abflugdatum FROM buchungen b JOIN fluege f ON b.FlugId = f.Id WHERE b.UserId = {userId}", UserBookingsGrid); @@ -38,6 +42,12 @@ namespace SkyTeam if (MessageBox.Show($"User {uid} löschen?", "Confirm", MessageBoxButton.YesNo) == MessageBoxResult.Yes) { + // Quelle: AI Assistant (Claude ai) + // Idee: Sicherheits Risiko Bewertung (Risk Assessment) + // Die KI hat angemerkt, dass String Interpolation ($"DELETE... {uid}") bei SQL Queries + // normalerweise ein No Go ist (SQL Injection Gefahr). Da wir hier aber sicherstellen, dass 'uid' + // zwingend ein Integer (Convert.ToInt32) aus unserer eigenen Datenquelle ist, ist es in diesem + // spezifischen Fall für ein Admin Tool vertretbar und spart Code. ExecuteSql($"DELETE FROM users WHERE Id={uid}"); LoadUsers(); UserBookingsGrid.ItemsSource = null; @@ -46,6 +56,7 @@ namespace SkyTeam private void LoadFlights() { + // Quelle: Im Unterricht gemacht string q = @"SELECT f.Id, f.Flugnummer, f.Abflugort, f.Zielort, f.Abflugdatum, f.Preis, z.Modell AS Plane, CONCAT(p.Vorname, ' ', p.Nachname) AS Pilot FROM fluege f @@ -68,7 +79,7 @@ namespace SkyTeam private void AddFlight_Click(object sender, RoutedEventArgs e) { - + // Quelle: Im Unterricht gemacht if (string.IsNullOrWhiteSpace(AddFromCombo.Text) || string.IsNullOrWhiteSpace(AddToCombo.Text) || AddDatePick.SelectedDate == null || @@ -95,7 +106,12 @@ namespace SkyTeam cmd.Parameters.AddWithValue("@from", AddFromCombo.Text); cmd.Parameters.AddWithValue("@to", AddToCombo.Text); cmd.Parameters.AddWithValue("@date", AddDatePick.SelectedDate.Value); + + // Quelle: Microsoft Learn + // Wir nutzen die eingebaute AddHours-Methode von DateTime, um automatisch + // ein fiktives Ankunftsdatum zu generieren (Abflug + 4 Stunden) cmd.Parameters.AddWithValue("@arr", AddDatePick.SelectedDate.Value.AddHours(4)); + cmd.Parameters.AddWithValue("@fnum", flightNum); cmd.Parameters.AddWithValue("@price", AddPriceTxt.Text); cmd.Parameters.AddWithValue("@plane", PlaneCombo.SelectedValue); @@ -104,7 +120,7 @@ namespace SkyTeam cmd.ExecuteNonQuery(); MessageBox.Show($"Flug {flightNum} erstellt!"); - LoadFlights(); + LoadFlights(); } } catch (Exception ex) { MessageBox.Show("Error: " + ex.Message); } @@ -116,11 +132,19 @@ namespace SkyTeam BindComboBox("SELECT Id, CONCAT(Vorname, ' ', Nachname) AS FullName FROM piloten WHERE IstVerfuegbar=1", PilotCombo, "FullName", "Id"); } + // Quelle: AI Assistant (Gemini) + // Idee: DRY Prinzip (Don't Repeat Yourself) / Refactoring + // Die KI hat vorgeschlagen, die sehr repetitiven SQL-Verbindungs und Adapter Logiken + // in universelle Hilfsmethoden (BindGrid, BindComboBox, ExecuteSql) auszulagern. + // Das reduziert den Code der Hauptmethoden enorm und macht die Klasse wartbarer. private void BindGrid(string q, DataGrid g) { try { using (var c = new MySqlConnection(DatenbankServices.GetConnection())) { c.Open(); var a = new MySqlDataAdapter(q, c); var t = new DataTable(); a.Fill(t); g.ItemsSource = t.DefaultView; } } catch { } } + // Quelle: Microsoft Learn + // Hier nutzen wir DisplayMemberPath für den Text, den der User sieht (z.B. das Flugzeugmodell) + // und SelectedValuePath für den Wert, der im Hintergrund für die Datenbankabfrage genutzt wird (z.B. die Id). private void BindComboBox(string q, ComboBox b, string d, string v) { try { using (var c = new MySqlConnection(DatenbankServices.GetConnection())) { c.Open(); var a = new MySqlDataAdapter(q, c); var t = new DataTable(); a.Fill(t); b.ItemsSource = t.DefaultView; b.DisplayMemberPath = d; b.SelectedValuePath = v; } } catch { } @@ -131,6 +155,7 @@ namespace SkyTeam try { using (var c = new MySqlConnection(DatenbankServices.GetConnection())) { c.Open(); new MySqlCommand(s, c).ExecuteNonQuery(); } } catch (Exception ex) { MessageBox.Show(ex.Message); } } + // Quelle: Im Unterricht gemacht private void ShowUsers_Click(object sender, RoutedEventArgs e) { UserManagementGrid.Visibility = Visibility.Visible; diff --git a/SkyTeam/AdminLoginPage.xaml.cs b/SkyTeam/AdminLoginPage.xaml.cs index 1be037a..236d64f 100644 --- a/SkyTeam/AdminLoginPage.xaml.cs +++ b/SkyTeam/AdminLoginPage.xaml.cs @@ -9,19 +9,28 @@ namespace SkyTeam { public partial class AdminLoginPage : Page { + // Quelle: Microsoft Learn + // Nutzung eines "Expression bodied members" (=>) für den Konstruktor + // Das macht Methoden oder Konstruktoren, die nur aus einer einzigen Zeile bestehen, + // deutlich kompakter und besser lesbar public AdminLoginPage() => InitializeComponent(); private void AdminLogin_Click(object sender, RoutedEventArgs e) { + // Quelle: Im Unterricht gemacht + string query = "SELECT Id, PasswortHash FROM users WHERE Email = @email AND Vorname = @user AND Rolle = @role"; try { + using (MySqlConnection conn = new MySqlConnection(DatenbankServices.GetConnection())) { conn.Open(); using (MySqlCommand cmd = new MySqlCommand(query, conn)) { + // Quelle: Im Unterricht gemacht + cmd.Parameters.AddWithValue("@email", AdminEmailBox.Text); cmd.Parameters.AddWithValue("@user", AdminUserBox.Text); cmd.Parameters.AddWithValue("@role", AdminRoleBox.Text); @@ -33,8 +42,16 @@ namespace SkyTeam string storedHash = reader.GetString("PasswortHash"); int dbId = reader.GetInt32("Id"); + // Quelle: Stack Overflow + // Genau wie beim normalen User-Login prüfen wir das Passwort lokal + // über die BCrypt-Bibliothek. Ein direkter Abgleich in der SQL-Datenbank + // (WHERE PasswortHash = @hash) ist unmöglich, da BCrypt dynamische Salts verwendet if (BCrypt.Net.BCrypt.Verify(AdminPassBox.Password, storedHash)) { + // Quelle: AI Assistant (Gemini) + // Konsistentes State Management + // Kommentar: Wir nutzen wieder den statischen SessionManager, den die KI für das + // Haupt Login vorgeschlagen hatte. So weiß das AdminDashboard sofort, wer eingeloggt ist SessionManager.CurrentUserId = dbId; SessionManager.CurrentUserName = AdminUserBox.Text; @@ -60,6 +77,7 @@ namespace SkyTeam } } + // Quelle: Im Unterricht gemacht private void Back_Click(object sender, RoutedEventArgs e) => NavigationService.Navigate(new LogInPage()); } } \ No newline at end of file diff --git a/SkyTeam/App.xaml.cs b/SkyTeam/App.xaml.cs index c0ed087..15ba53c 100644 --- a/SkyTeam/App.xaml.cs +++ b/SkyTeam/App.xaml.cs @@ -1,24 +1,35 @@ using BCrypt.Net; using MySql.Data.MySqlClient; +using System; using System.Globalization; -using System.Reflection; +using System.Threading; using System.Windows; namespace SkyTeam { public partial class App : Application { - // Beim Start der Anwendung einen Standard Admin-Benutzer erstellen, falls keiner existiert + // Beim Start der Anwendung einen Standard Admin Benutzer erstellen, falls keiner existiert public App() { + // Quelle: Stack Overflow + // Durch das Setzen der CurrentUICulture direkt im App-Konstruktor stellen wir sicher, + // dass die gesamte Anwendung (alle Pages und Windows) von Anfang an die gleiche Spracheinstellung + // nutzt. Das verhindert Inkonsistenzen beim Laden der ersten Seite. Thread.CurrentThread.CurrentUICulture = new CultureInfo("de"); - Thread.CurrentThread.CurrentUICulture = new CultureInfo("de"); + + // Quelle: AI Assistant (Gemini) + // Idee: Code-Bereinigung / Refactoring + // Die Zuweisung der Culture stand hier ursprünglich doppelt. Die KI hat beim + // Code Review darauf hingewiesen, dass eine einmalige Zuweisung ausreicht, um Redundanzen + // zu vermeiden. Die zweite Zeile wurde entfernt. + CreateDefaultAdmin(); } private void CreateDefaultAdmin() { - string connectionString = DatenbankServices.GetConnection(); + string connectionString = DatenbankServices.GetConnection(); try { @@ -26,18 +37,26 @@ namespace SkyTeam { conn.Open(); + // Quelle: Stack Overflow + // Dieses Architektur-Muster nennt sich "Database Seeding". Es stellt sicher, + // dass das System nach einer Neuinstallation sofort nutzbar ist, da automatisch ein + // Root-Account existiert, ohne dass manuelle SQL Eingriffe nötig sind. string checkQuery = "SELECT COUNT(*) FROM users WHERE Rolle = 'Admin'"; MySqlCommand checkCmd = new MySqlCommand(checkQuery, conn); long count = (long)checkCmd.ExecuteScalar(); if (count == 0) { + // Quelle: Reddit + string hashedPassword = BCrypt.Net.BCrypt.HashPassword("admin"); string insertQuery = @" INSERT INTO users (Vorname, Nachname, Email, PasswortHash, Rolle, Stadt, CreatedAt) VALUES ('System', 'Root', 'admin@skyteam.com', @hash, 'Admin', 'HQ', NOW())"; + // Quelle: Im Unterricht gemacht + MySqlCommand insertCmd = new MySqlCommand(insertQuery, conn); insertCmd.Parameters.AddWithValue("@hash", hashedPassword); insertCmd.ExecuteNonQuery(); @@ -46,9 +65,9 @@ namespace SkyTeam } } } - catch(Exception ex) { - - MessageBox.Show("Fehler beim Erstellen des Standard Admins: " + ex.Message); + catch (Exception ex) + { + MessageBox.Show("Fehler beim Erstellen des Standard Admins: " + ex.Message); } } } diff --git a/SkyTeam/DatenbankServices.cs b/SkyTeam/DatenbankServices.cs index 9d42372..a1f0054 100644 --- a/SkyTeam/DatenbankServices.cs +++ b/SkyTeam/DatenbankServices.cs @@ -10,7 +10,7 @@ namespace SkyTeam { static class DatenbankServices { - private static readonly string connectionString = File.ReadAllText("connectionstring.txt"); + private static readonly string connectionString = "Server=mysql.pb.bib.de;uid=pbt3h24akh;pwd=Dd3dwQgPeNxW;database=pbt3h24akh_SkyTeam;"; public static string GetConnection() { diff --git a/SkyTeam/HomePage.xaml.cs b/SkyTeam/HomePage.xaml.cs index cc9081f..a7728b9 100644 --- a/SkyTeam/HomePage.xaml.cs +++ b/SkyTeam/HomePage.xaml.cs @@ -3,6 +3,7 @@ using System.Windows.Controls; namespace SkyTeam { + // in unterricht schonmal gemacht , hier nochmal public partial class NavigationPage : Page { public NavigationPage() diff --git a/SkyTeam/LogInPage.xaml.cs b/SkyTeam/LogInPage.xaml.cs index 13da875..23d06a6 100644 --- a/SkyTeam/LogInPage.xaml.cs +++ b/SkyTeam/LogInPage.xaml.cs @@ -11,23 +11,27 @@ namespace SkyTeam { public LogInPage() { + InitializeComponent(); } private void AdminLink_Click(object sender, RoutedEventArgs e) { + // Quelle: Im Unterricht gemacht + if (Application.Current.MainWindow is MainWindow mainWindow) { mainWindow.MainFrame.Navigate(new AdminLoginPage()); } } - - private void LogInButton_Click(object sender, RoutedEventArgs e) + private void LogInButton_Click(object sender, RoutedEventArgs e) { string email = BenutzernameTextBox.Text; string password = PasswortTextBox.Password; + // Quelle: Im Unterricht gemacht + // Basis Validierung auf leere Felder if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(password)) { MessageBox.Show("Bitte Email und Passwort eingeben."); @@ -57,14 +61,24 @@ namespace SkyTeam return; } + // Quelle: Stack Overflow "How to verify a BCrypt hash" + // Man kann gehashte Passwörter NICHT direkt im SQL Query vergleichen + // (z.B. WHERE Hash = @hash), da BCrypt jedes Mal einen neuen, zufälligen Salt generiert. + // Wir müssen erst den gespeicherten Hash aus der DB laden und dann die Verify Methode + // der BCrypt-Bibliothek nutzen, um das Klartext passwort damit zu prüfen string storedHash = reader.GetString("PasswortHash"); - + if (!BCrypt.Net.BCrypt.Verify(password, storedHash)) { MessageBox.Show("Falsches Passwort."); return; } + // Quelle: AI Assistant (chat gpt) + // Idee: Globales State-Management über eine statische Klasse (SessionManager) + // Kommentar: Anstatt die User ID mühsam über jeden Seitenaufruf hinweg in den Konstruktoren + // weiterzureichen, hat die KI vorgeschlagen, eine statische SessionManager Klasse zu nutzen. + // So sind User-ID, Name und Rolle global für die gesamte Laufzeit abrufbar. SessionManager.CurrentUserId = reader.GetInt32("Id"); SessionManager.CurrentUserName = reader.GetString("Vorname"); SessionManager.Role = reader.GetString("Rolle"); @@ -83,17 +97,24 @@ namespace SkyTeam private void anmeldungsButton_Click(object sender, RoutedEventArgs e) { + // Quelle: Im Unterricht gemacht ((MainWindow)Application.Current.MainWindow) .MainFrame.Navigate(new RegistrationPage()); } private void Page_Loaded(object sender, RoutedEventArgs e) { + // Quelle: Im Unterricht gemacht + // Setzt den Cursor direkt beim Laden der Seite ins Benutzernamen-Feld. BenutzernameTextBox.Focus(); } private void BenutzernameTextBox_PreviewKeyDown(object sender, KeyEventArgs e) { + // Quelle: Stack Overflow "WPF Move focus on enter key" + // Kommentar: Ein UX-Feature (User Experience). Wenn der User im Textfeld 'Enter' oder die 'Pfeil Runter' Taste + // drückt, generieren wir einen TraversalRequest. Dadurch springt der Fokus automatisch ins nächste UI-Element + // (das Passwort Feld), ohne dass der User die Maus benutzen muss. if (e.Key == Key.Down || e.Key == Key.Enter) { TraversalRequest request = @@ -104,4 +125,4 @@ namespace SkyTeam } } } -} +} \ No newline at end of file diff --git a/SkyTeam/MainWindow.xaml b/SkyTeam/MainWindow.xaml index 0694200..cd77718 100644 --- a/SkyTeam/MainWindow.xaml +++ b/SkyTeam/MainWindow.xaml @@ -10,6 +10,7 @@ Width="1100" WindowStartupLocation="CenterScreen" WindowState="Maximized"> + diff --git a/SkyTeam/MainWindow.xaml.cs b/SkyTeam/MainWindow.xaml.cs index acb6674..f360086 100644 --- a/SkyTeam/MainWindow.xaml.cs +++ b/SkyTeam/MainWindow.xaml.cs @@ -5,6 +5,7 @@ namespace SkyTeam { public partial class MainWindow : Window { + // nichts besonderes, hier wird nur die LoginPage als erstes angezeigt public MainWindow() { InitializeComponent(); diff --git a/SkyTeam/Regestrieren.xaml.cs b/SkyTeam/Regestrieren.xaml.cs index 7c0c0a8..de74bf1 100644 --- a/SkyTeam/Regestrieren.xaml.cs +++ b/SkyTeam/Regestrieren.xaml.cs @@ -1,7 +1,7 @@ using System; using System.Windows; using System.Windows.Controls; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; using MySql.Data.MySqlClient; using BCrypt.Net; @@ -11,11 +11,15 @@ namespace SkyTeam { public RegistrationPage() { + // Quelle: Im Unterricht gemacht + // Standard-Initialisierung der WPF Komponenten InitializeComponent(); } private void RegisterButton_Click(object sender, RoutedEventArgs e) { + // Quelle: Im Unterricht gemacht + // Grundlegende Validierung, ob die Pflichtfelder ausgefüllt wurden if (string.IsNullOrWhiteSpace(EmailTextBox.Text) || string.IsNullOrWhiteSpace(PasswordBox.Password)) { MessageBox.Show("Bitte geben Sie Email und Passwort ein."); @@ -36,6 +40,10 @@ namespace SkyTeam { conn.Open(); + // Quelle: Stack Overflow - "Check if a row exists with a specific value" + // Link: https://stackoverflow.com/questions/2788543/check-if-a-row-exists-with-a-specific-value-in-a-database + // Wir nutzen ExecuteScalar() anstelle eines Readers, da wir nur wissen wollen, + // ob die Email bereits existiert (COUNT > 0). Das ist wesentlich performanter string checkQuery = "SELECT COUNT(*) FROM users WHERE Email = @email"; using (MySqlCommand checkCmd = new MySqlCommand(checkQuery, conn)) { @@ -46,10 +54,14 @@ namespace SkyTeam { MessageBox.Show("Sie haben bereits ein Konto mit dieser E-Mail. Bitte löschen Sie es, bevor Sie ein neues erstellen.", "Konto existiert bereits", MessageBoxButton.OK, MessageBoxImage.Error); - return; + return; } } + // Quelle: Reddit - r/csharp "How should I store passwords in my database?" + // Link: https://www.reddit.com/r/csharp/comments/7qcx8f/how_should_i_store_passwords_in_my_database/ + // Laut Community Konsens sollten Passwörter niemals im Klartext gespeichert werden + // Wir nutzen die BCrypt Bibliothek, die automatisch Salting und Hashing übernimmt , das ist schon mal in Unterricht behandelt worden string hashedPassword = BCrypt.Net.BCrypt.HashPassword(PasswordBox.Password); string insertQuery = "INSERT INTO users (Vorname, Nachname, Email, PasswortHash, Rolle, Stadt, Anrede, Geburtsdatum) " + "VALUES (@vorname, @nachname, @email, @password, 'User', @stadt, @anrede, @geburtsdatum)"; @@ -59,11 +71,19 @@ namespace SkyTeam string selectedAnrede = (SalutationComboBox.SelectedItem as ComboBoxItem)?.Content.ToString(); DateTime? selectedDate = BirthDatePicker.SelectedDate; + // Quelle: Im Unterricht gemacht + // Kommentar: Standard Parameter-Binding zum Schutz vor SQL-Injection cmd.Parameters.AddWithValue("@vorname", FirstNameTextBox.Text); cmd.Parameters.AddWithValue("@nachname", LastNameTextBox.Text); cmd.Parameters.AddWithValue("@email", emailToCheck); cmd.Parameters.AddWithValue("@password", hashedPassword); cmd.Parameters.AddWithValue("@stadt", CityTextBox.Text); + + // Quelle: Microsoft Learn - "DBNull.Value Field" + // Link: https://learn.microsoft.com/en-us/dotnet/api/system.dbnull.value + // Wenn optionale Felder (wie Anrede oder Geburtsdatum) leer bleiben, + // können wir nicht einfach 'null' in C# übergeben. Die Datenbank erwartet explizit + // das Objekt 'DBNull.Value', damit die Spalte korrekt als NULL markiert wird cmd.Parameters.AddWithValue("@anrede", selectedAnrede ?? (object)DBNull.Value); cmd.Parameters.AddWithValue("@geburtsdatum", selectedDate.HasValue ? selectedDate.Value : (object)DBNull.Value); @@ -73,6 +93,11 @@ namespace SkyTeam MessageBox.Show("Dein Konto wurde erfolgreich angelegt!"); + // Quelle: AI Assistant (Gemini) + // Idee: Navigation aus einer Page heraus, die in einem Frame gehostet wird. + // Kommentar: Die KI hat darauf hingewiesen, dass eine Page (wie die RegistrationPage) + // nicht direkt navigieren sollte, wenn sie im MainFrame des MainWindows liegt. + // Wir müssen erst auf das MainWindow zugreifen, um dessen Frame für die Navigation zu nutzen. if (Application.Current.MainWindow is MainWindow mainWindow) { mainWindow.MainFrame.Navigate(new LogInPage()); @@ -99,6 +124,11 @@ namespace SkyTeam try { + // Quelle: Microsoft Learn - "How to verify that strings are in valid email format" + // Link: https://learn.microsoft.com/en-us/dotnet/standard/base-types/how-to-verify-that-strings-are-in-valid-email-format + // Kommentar: Wir nutzen den offiziell empfohlenen Regex-Ausdruck von Microsoft, + // kombiniert mit einem Timeout (250ms), um Denial-of-Service-Angriffe durch + // extrem lange oder fehlerhafte Strings (ReDoS) zu verhindern. return Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$", RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250)); diff --git a/SkyTeam/Screenshot 2026-03-04 100331.ico b/SkyTeam/Screenshot 2026-03-04 100331.ico new file mode 100644 index 0000000..8551563 Binary files /dev/null and b/SkyTeam/Screenshot 2026-03-04 100331.ico differ diff --git a/SkyTeam/SettingsPage.xaml.cs b/SkyTeam/SettingsPage.xaml.cs index 7cf6d11..7fd57d8 100644 --- a/SkyTeam/SettingsPage.xaml.cs +++ b/SkyTeam/SettingsPage.xaml.cs @@ -15,6 +15,9 @@ namespace SkyTeam { InitializeComponent(); + // Quelle: Stack Overflow "How to compare SolidColorBrush in WPF" + // Wir lesen die globale Ressource aus und prüfen die Farbe, um sicherzustellen, + // dass der Toggle Switch beim Wechseln der Seite den richtigen Zustand (An/Aus) anzeigt var bgBrush = Application.Current.Resources["PageBackground"] as SolidColorBrush; if (bgBrush != null && bgBrush.Color == Color.FromRgb(30, 30, 30)) { @@ -49,6 +52,10 @@ namespace SkyTeam DarkModeToggle.Content = "Aus"; } + // Quelle: AI Assistant (chat gpt) + // Idee: Refactoring / DRY-Prinzip (Don't Repeat Yourself) + // Die KI hat vorgeschlagen, das Zuweisen der Application.Current.Resources in eine + // separate Hilfsmethode auszulagern, um den Code in den Checked/Unchecked Events sauberer zu halten. private void SetRes(string key, Color color) { Application.Current.Resources[key] = new SolidColorBrush(color); @@ -63,8 +70,15 @@ namespace SkyTeam using (MySqlConnection conn = new MySqlConnection(DatenbankServices.GetConnection())) { conn.Open(); + + // Quelle: Reddit - r/csharp "Best way to delete user with related data?" + // Aufgrund von Foreign Key Constraints in der Datenbank + // müssen zwingend erst die Buchungen des Users gelöscht werden, bevor der User + // selbst gelöscht werden darf. Sonst wirft die Datenbank einen Fehler string deleteBookings = "DELETE FROM buchungen WHERE UserId = @uid"; MySqlCommand cmd1 = new MySqlCommand(deleteBookings, conn); + + // Quelle: Im Unterricht gemacht cmd1.Parameters.AddWithValue("@uid", SessionManager.CurrentUserId); cmd1.ExecuteNonQuery(); @@ -85,12 +99,15 @@ namespace SkyTeam } } + // Quelle: Im Unterricht gemacht + // Lambda-Ausdrücke (=>) für simple Seitenwechsel über den NavigationService private void HomeButton_Click(object sender, RoutedEventArgs e) => NavigationService.Navigate(new NavigationPage()); private void BookingsButton_Click(object sender, RoutedEventArgs e) => NavigationService.Navigate(new BuchungenPage()); - private void SettingsButton_Click(object sender, RoutedEventArgs e) => NavigationService.Navigate(new SettingsPage()); + private void SettingsButton_Click(object sender, RoutedEventArgs e) => NavigationService.Navigate(new SettingsPage()); + private void LogoutButton_Click(object sender, RoutedEventArgs e) { - SessionManager.CurrentUserId = 0; + SessionManager.CurrentUserId = 0; NavigationService.Navigate(new LogInPage()); } @@ -98,6 +115,11 @@ namespace SkyTeam { if (LanguageComboBox.SelectedItem is ComboBoxItem selectedItem) { + // Quelle: Microsoft Learn "FlowDirection Enumeration" & Stack Overflow "WPF RTL Support" + // Link : https://learn.microsoft.com/en-us/dotnet/api/system.windows.flowdirection + // Wir setzen nicht nur die UI Culture auf die ausgewählte Sprache, sondern + // passen für Arabisch ("ar") auch dynamisch die 'FlowDirection' auf Right-To-Left an, + // damit das Layout der Seite korrekt gespiegelt wird string culture = selectedItem.Tag.ToString(); Thread.CurrentThread.CurrentUICulture = new CultureInfo(culture); @@ -106,6 +128,9 @@ namespace SkyTeam else this.FlowDirection = FlowDirection.LeftToRight; + // Quelle: Stack Overflow "How to refresh WPF page after changing culture?" + // Durch das erneute Navigieren auf die gleiche Seite (SettingsPage) wird + // das UI gezwungen, sich mit der neu gesetzten Sprache und FlowDirection neu zu rendern NavigationService.Navigate(new SettingsPage()); } } diff --git a/SkyTeam/SkyTeam.csproj b/SkyTeam/SkyTeam.csproj index 8d7c06a..e449673 100644 --- a/SkyTeam/SkyTeam.csproj +++ b/SkyTeam/SkyTeam.csproj @@ -1,13 +1,17 @@  - WinExe - net8.0-windows + Exe + net8.0-windows7.0 enable enable true + + + + diff --git a/SkyTeam/reservierungsSuche.xaml.cs b/SkyTeam/reservierungsSuche.xaml.cs index 6263657..b36db6a 100644 --- a/SkyTeam/reservierungsSuche.xaml.cs +++ b/SkyTeam/reservierungsSuche.xaml.cs @@ -9,19 +9,27 @@ namespace SkyTeam { public ReservierungssuchePage() { + InitializeComponent(); } private void SearchFlights_Click(object sender, RoutedEventArgs e) { + // Simples Auslesen der Benutzereingaben aus den Textboxen und dem DatePicker string from = FromBox.Text; string to = ToBox.Text; DateTime? date = DateBox.SelectedDate; + // Quelle: Stack Overflow "Passing parameters between pages in WPF" + // Um Daten (wie Suchkriterien) sicher an die nächste Seite zu übergeben, nutzen wir hier + // den überladenen Konstruktor der Ziel-Page ("verfuegbareFluge"). Laut Community-Konsens ist das + // die sauberste und direkteste Methode für einfache Datenübergaben in Standard-WPF-Anwendungen. NavigationService.Navigate(new verfuegbareFluge(from, to, date)); } + // Quelle: Im Unterricht gemacht + // Lambda-Ausdrücke (=>) für kurze und übersichtliche Seitenwechsel über den NavigationService private void HomeButton_Click(object sender, RoutedEventArgs e) => NavigationService.Navigate(new NavigationPage()); private void BookingsButton_Click(object sender, RoutedEventArgs e) => NavigationService.Navigate(new BuchungenPage()); private void SettingsButton_Click(object sender, RoutedEventArgs e) => NavigationService.Navigate(new SettingsPage()); diff --git a/SkyTeam/verfuegbareFluge.xaml.cs b/SkyTeam/verfuegbareFluge.xaml.cs index 2582200..f9676de 100644 --- a/SkyTeam/verfuegbareFluge.xaml.cs +++ b/SkyTeam/verfuegbareFluge.xaml.cs @@ -26,11 +26,13 @@ namespace SkyTeam private void LoadFlights() { + // Source: Stack Overflow "What is the purpose of using WHERE 1=1 in SQL statements?" + // Link: https://stackoverflow.com/questions/1264681/what-is-the-purpose-of-using-where-1-1-in-sql-statements string query = @"SELECT f.Id, f.Flugnummer, f.Abflugort AS 'From', f.Zielort AS 'To', z.Modell AS Plane, f.Abflugdatum AS Date FROM fluege f JOIN flugzeuge z ON f.FlugzeugId = z.Id - WHERE 1=1"; + WHERE 1=1"; if (!string.IsNullOrWhiteSpace(_fromCity)) { @@ -86,9 +88,13 @@ namespace SkyTeam return; } + // Source: Stack Overflow "Get selected row item in DataGrid WPF" + // Link: https://stackoverflow.com/questions/3913580/get-selected-row-item-in-datagrid-wpf + DataRowView row = (DataRowView)AvailableFlightsDataGrid.SelectedItem; int flightId = Convert.ToInt32(row["Id"]); + if (SessionManager.CurrentUserId == 0) { MessageBox.Show("Fehler: Nicht eingeloggt."); @@ -117,6 +123,8 @@ namespace SkyTeam } } + // quelle: Microsoft Learn "NavigationService.Navigate Method" + private void HomeButton_Click(object sender, RoutedEventArgs e) => NavigationService.Navigate(new NavigationPage()); private void BookingsButton_Click(object sender, RoutedEventArgs e) => NavigationService.Navigate(new BuchungenPage()); private void SettingsButton_Click(object sender, RoutedEventArgs e) => NavigationService.Navigate(new SettingsPage());