From 4a0f5dc0bd46cc162854baae93aea32eeac6d2dd Mon Sep 17 00:00:00 2001 From: younes elhaddoury Date: Thu, 5 Mar 2026 12:32:01 +0100 Subject: [PATCH] comments und quellen hinzufugt --- Screenshot 2026-03-04 100331.ico | Bin 0 -> 4022 bytes SkyTeam/AdminDashBoard.xaml.cs | 29 +++++++++++++++++-- SkyTeam/AdminLoginPage.xaml.cs | 18 ++++++++++++ SkyTeam/App.xaml.cs | 33 +++++++++++++++++----- SkyTeam/DatenbankServices.cs | 2 +- SkyTeam/HomePage.xaml.cs | 1 + SkyTeam/LogInPage.xaml.cs | 29 ++++++++++++++++--- SkyTeam/MainWindow.xaml | 1 + SkyTeam/MainWindow.xaml.cs | 1 + SkyTeam/Regestrieren.xaml.cs | 34 +++++++++++++++++++++-- SkyTeam/Screenshot 2026-03-04 100331.ico | Bin 0 -> 4022 bytes SkyTeam/SettingsPage.xaml.cs | 29 +++++++++++++++++-- SkyTeam/SkyTeam.csproj | 8 ++++-- SkyTeam/reservierungsSuche.xaml.cs | 8 ++++++ SkyTeam/verfuegbareFluge.xaml.cs | 10 ++++++- 15 files changed, 182 insertions(+), 21 deletions(-) create mode 100644 Screenshot 2026-03-04 100331.ico create mode 100644 SkyTeam/Screenshot 2026-03-04 100331.ico diff --git a/Screenshot 2026-03-04 100331.ico b/Screenshot 2026-03-04 100331.ico new file mode 100644 index 0000000000000000000000000000000000000000..85515632b8d738e48a829680c92eb19339876c3f GIT binary patch literal 4022 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 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 0000000000000000000000000000000000000000..85515632b8d738e48a829680c92eb19339876c3f GIT binary patch literal 4022 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 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());