diff --git a/src/main/java/app/Main.java b/src/main/java/app/Main.java index b6dd8e0..611e87f 100644 --- a/src/main/java/app/Main.java +++ b/src/main/java/app/Main.java @@ -20,20 +20,27 @@ import javafx.scene.Scene; import javafx.stage.Stage; +/** + * Entry point for the GiveHope application. + * Initializes the database, fetches organizations from Innsamlingskontrollen, + * and launches the JavaFX application. + */ public class Main extends Application{ public static void main(String[] args) { launch(args); } + /** + * Starts the JavaFX application by initializing the database, + * loading organizations from Innsamlingskontrollen, and displaying the main view. + * + * @param stage the primary stage for the application + * @throws Exception if the FXML file cannot be loaded + */ @Override public void start(Stage stage) throws Exception { Database.initializeDatabase(); - System.out.println("DB path: " + new java.io.File("givehope.db").getAbsolutePath()); - OrganizationDetails details = InnsamlingskontrollenClient.fetchDetails( - "https://www.innsamlingskontrollen.no/organisasjoner/caritas-norge/" - ); - System.out.println("Description: " + details.getDescription()); - System.out.println("Logo: " + details.getLogoUrl()); + OrganizationDao orgDao = new OrganizationDao(); Organization[] orgs = InnsamlingskontrollenClient.fetchOrganizations(); for (Organization org : orgs) { @@ -50,6 +57,12 @@ public void start(Stage stage) throws Exception { stage.show(); } + /** + * Inserts test users and donations into the database if they do not already exist. + * Used for development and demonstration purposes. + * + * @throws SQLException if a database error occurs + */ private void addTestdata() throws SQLException { UserDao userDao = new UserDao(); DonationDao donationDao = new DonationDao(); diff --git a/src/main/java/application/user/UserEditProfile.java b/src/main/java/application/user/UserEditProfile.java index d4401c4..a6008fc 100644 --- a/src/main/java/application/user/UserEditProfile.java +++ b/src/main/java/application/user/UserEditProfile.java @@ -5,18 +5,36 @@ import persistence.dao.UserDao; import persistence.dao.UserRepository; +/** + * Use case class for editing a user's profile information. + * Handles validation, optional password hashing and persistence of updated user data. + */ public class UserEditProfile { private final PasswordHasher hasher; private final UserRepository repo; - + /** + * Constructs a new {@code UserEditProfile} with the given hasher and repository. + * + * @param hasher the password hasher used to hash new passwords + * @param repo the repository used to persist updated user data + */ public UserEditProfile(PasswordHasher hasher, UserRepository repo) { this.hasher = hasher; this.repo = repo; } - + /** + * Updates the user's profile with the provided information. + * If the password field is non-blank, it will be hashed and updated. + * + * @param user the user to update + * @param username the new username + * @param email the new email address + * @param phoneNumber the new phone number + * @param password the new password, or blank/null to keep the current password + */ public void execute(User user, String username, String email, String phoneNumber, String password) { user.setUsername(username); user.setEmail(email); diff --git a/src/main/java/application/user/UserLogin.java b/src/main/java/application/user/UserLogin.java deleted file mode 100644 index 4392ee4..0000000 --- a/src/main/java/application/user/UserLogin.java +++ /dev/null @@ -1,36 +0,0 @@ -package application.user; - -import application.security.PasswordHasher; -import domain.user.User; -import persistence.dao.UserRepository; - -public class UserLogin { - - private final PasswordHasher hasher; - private final UserRepository repo; - - public UserLogin(PasswordHasher hasher, UserRepository repo) { - this.hasher = hasher; - this.repo = repo; - } - - public User execute(String login, String password) { - // normalize (trim etc.) - login = login.trim().toLowerCase(); - - User user = repo.findByLogin(login) - .orElseThrow(() -> new IllegalArgumentException("Invalid credentials")); - - // hash password (optional) - String hashedInput = hasher.hash(password); - - // compare hashed input with stored hash - if (!hashedInput.equals(user.getPassword())) { - throw new IllegalArgumentException("Invalid password"); - } - - System.out.println("Loggin in"); - // return user (if match) - return user; - } -} diff --git a/src/main/java/application/user/UserSignIn.java b/src/main/java/application/user/UserSignIn.java index f7ea596..7e68429 100644 --- a/src/main/java/application/user/UserSignIn.java +++ b/src/main/java/application/user/UserSignIn.java @@ -36,7 +36,7 @@ public UserSignIn(PasswordHasher hasher, UserRepository repo) { * @return user object, if it exists. */ public User execute(String login, String password) { - login.trim().toLowerCase(); + login = login.trim().toLowerCase(); User user = repo.findByLogin(login) .orElseThrow(() -> new IllegalArgumentException("Invalid credentials")); diff --git a/src/main/java/domain/donation/Donation.java b/src/main/java/domain/donation/Donation.java index 53e1063..47385b2 100644 --- a/src/main/java/domain/donation/Donation.java +++ b/src/main/java/domain/donation/Donation.java @@ -19,11 +19,12 @@ public class Donation { private final Organization organization; /** - * Donation takes in parameters that is needed to define what a donation is. - * Requires both user and organization to be not null. - * @param amount - * @param user - * @param organization + * Creates a new donation with the current date and time. + * + * @param amount the donation amount, must be greater than 0 + * @param user the user making the donation, cannot be null + * @param organization the organization receiving the donation, cannot be null + * @throws IllegalArgumentException if amount is zero or negative */ public Donation(BigDecimal amount, User user, Organization organization) { Objects.requireNonNull(amount, "Amount cannot be null"); @@ -37,6 +38,26 @@ public Donation(BigDecimal amount, User user, Organization organization) { this.organization = Objects.requireNonNull(organization, "Organization cannot be null"); } + /** + * Creates a donation with an existing date and time, used when loading from the database. + * + * @param amount the donation amount, must be greater than 0 + * @param user the user making the donation, cannot be null + * @param organization the organization receiving the donation, cannot be null + * @param dateTime the date and time of the donation, cannot be null + * @throws IllegalArgumentException if amount is zero or negative + */ + public Donation(BigDecimal amount, User user, Organization organization, LocalDateTime dateTime) { + Objects.requireNonNull(amount, "Amount cannot be null"); + if (amount.compareTo(BigDecimal.ZERO) <= 0) { + throw new IllegalArgumentException("Amount must be greater than 0"); + } + this.amount = amount; + this.dateTime = Objects.requireNonNull(dateTime, "DateTime cannot be null"); + this.user = Objects.requireNonNull(user, "User cannot be null"); + this.organization = Objects.requireNonNull(organization, "Organization cannot be null"); + } + /** * Sets the database-generated ID for this donation. diff --git a/src/main/java/persistence/dao/DonationDao.java b/src/main/java/persistence/dao/DonationDao.java index dcf6cb1..a1f07ff 100644 --- a/src/main/java/persistence/dao/DonationDao.java +++ b/src/main/java/persistence/dao/DonationDao.java @@ -9,6 +9,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import persistence.db.Database; @@ -28,10 +29,6 @@ public class DonationDao { * @throws RuntimeException if a database error occurs during the insert */ public void insert(Donation donation) { - System.out.println("Inserting donation:"); - System.out.println(" user_id = " + donation.getUser().getID()); - System.out.println(" org_id = " + donation.getOrganization().getOrgNumber()); - String sql = "INSERT INTO donation(amount, donation_date" + ", user_id, organization_id) VALUES(?, ?, ?, ?)"; @@ -80,10 +77,12 @@ private Donation mapRow(ResultSet rs) throws SQLException { org.setPreApproved(rs.getInt("is_pre_approved") == 1); + Donation donation = new Donation( new BigDecimal(rs.getString("amount")), user, - org + org, + LocalDateTime.parse(rs.getString("donation_date")) ); donation.setId(rs.getLong("id")); return donation; diff --git a/src/main/java/persistence/dao/FavoriteDao.java b/src/main/java/persistence/dao/FavoriteDao.java index 2e262e2..57a862a 100644 --- a/src/main/java/persistence/dao/FavoriteDao.java +++ b/src/main/java/persistence/dao/FavoriteDao.java @@ -10,6 +10,11 @@ import java.util.ArrayList; import java.util.List; +/** + * Data Access Object for favorite organization-related database operations. + * Provides methods for adding, removing, retrieving and checking + * a user's saved favorite organizations. + */ public class FavoriteDao { diff --git a/src/main/java/persistence/dao/UserDao.java b/src/main/java/persistence/dao/UserDao.java index 90f57b2..5283890 100644 --- a/src/main/java/persistence/dao/UserDao.java +++ b/src/main/java/persistence/dao/UserDao.java @@ -204,6 +204,12 @@ private User mapUser(ResultSet rs) throws SQLException { return user; } + /** + * Updates an existing user's profile information in the database. + * + * @param user the {@link User} containing the updated information to persist + * @throws RuntimeException if a database error occurs during the update + */ public void updateUser(User user) { String sql = "UPDATE user SET user_name = ?, phone_number = ?, e_mail = ?, password_hash = ? WHERE id = ?"; diff --git a/src/main/java/ui/Page.java b/src/main/java/ui/Page.java index 77cac0f..4a3597b 100644 --- a/src/main/java/ui/Page.java +++ b/src/main/java/ui/Page.java @@ -6,15 +6,15 @@ */ public enum Page { HOME("Home.fxml"), - PROFILE("MyProfile.fxml"), - SIGN_IN("SignIn.fxml"), - REGISTER("Register.fxml"), - ORGANIZATIONS("OrganizationsView.fxml"), - MY_PROFILE("MyProfile.fxml"), - DONATION_NOT_LOGGED_IN("DonationNotLoggedIn.fxml"), - DONATION_AMOUNT("DonationAmount.fxml"), - DONATION_PAYMENT("DonationPayment.fxml"), - DONATION_CONFIRMATION("DonationConfirmation.fxml"); + PROFILE("profileView/MyProfile.fxml"), + SIGN_IN("auth/SignIn.fxml"), + REGISTER("auth/Register.fxml"), + ORGANIZATIONS("organizationView/OrganizationsView.fxml"), + MY_PROFILE("profileView/MyProfile.fxml"), + DONATION_NOT_LOGGED_IN("donationView/DonationNotLoggedIn.fxml"), + DONATION_AMOUNT("donationView/DonationAmount.fxml"), + DONATION_PAYMENT("donationView/DonationPayment.fxml"), + DONATION_CONFIRMATION("donationView/DonationConfirmation.fxml"); private final String fileName; diff --git a/src/main/java/ui/controller/MainController.java b/src/main/java/ui/controller/MainController.java index 6e7295b..40d5414 100644 --- a/src/main/java/ui/controller/MainController.java +++ b/src/main/java/ui/controller/MainController.java @@ -16,7 +16,14 @@ import ui.DonationSession; import ui.Page; import ui.DonationSessionAware; +import ui.controller.auth.RegisterController; +import ui.controller.auth.SignInController; import ui.controller.donation.DonationFlowController; +import ui.controller.organization.OrganizationController; +import ui.controller.organization.OrganizationDetailController; +import ui.controller.organization.SignInRequiredController; +import ui.controller.profile.MyProfileController; + import java.io.IOException; /** @@ -37,6 +44,10 @@ public class MainController { private DonationSession donationSession; private DonationFlowController donationFlowController; + /** + * Initializes the main controller by setting up the donation flow + * and navbar navigation callbacks, then loads the home page. + */ public void initialize() { donationFlowController = new DonationFlowController(mainPane, navbarController); donationFlowController.setOnNavigate(page -> { @@ -127,7 +138,7 @@ public void loadDonationPage(Organization org) throws IOException { public void showOrganizationDetail(Organization org, Button btn) { try { FXMLLoader loader = new FXMLLoader( - getClass().getResource("/view/OrganizationDetail.fxml")); + getClass().getResource("/view/organizationView/OrganizationDetail.fxml")); Parent root = loader.load(); OrganizationDetails details = @@ -182,7 +193,7 @@ public void showOrganizationDetail(Organization org, Button btn) { public void showSignInRequired() { try { FXMLLoader loader = new FXMLLoader( - getClass().getResource("/view/SignInRequired.fxml")); + getClass().getResource("/view/organizationView/SignInRequired.fxml")); Parent root = loader.load(); SignInRequiredController controller = loader.getController(); diff --git a/src/main/java/ui/controller/NavbarController.java b/src/main/java/ui/controller/NavbarController.java index af0d65f..3c070e2 100644 --- a/src/main/java/ui/controller/NavbarController.java +++ b/src/main/java/ui/controller/NavbarController.java @@ -65,7 +65,7 @@ public void goHome() { } /** - * Navigates to the sign in page or profile page depending on authentication state. + * Navigates to the sign-in page or profile page depending on authentication state. */ @FXML public void goToSignIn() { diff --git a/src/main/java/ui/controller/RegisterController.java b/src/main/java/ui/controller/auth/RegisterController.java similarity index 97% rename from src/main/java/ui/controller/RegisterController.java rename to src/main/java/ui/controller/auth/RegisterController.java index db662f8..7f480dc 100644 --- a/src/main/java/ui/controller/RegisterController.java +++ b/src/main/java/ui/controller/auth/RegisterController.java @@ -1,4 +1,4 @@ -package ui.controller; +package ui.controller.auth; import application.user.UserRegister; import javafx.fxml.FXML; @@ -6,6 +6,7 @@ import javafx.scene.control.PasswordField; import javafx.scene.control.TextField; import ui.Page; +import ui.controller.NavigationAware; import java.util.function.Consumer; diff --git a/src/main/java/ui/controller/SignInController.java b/src/main/java/ui/controller/auth/SignInController.java similarity index 96% rename from src/main/java/ui/controller/SignInController.java rename to src/main/java/ui/controller/auth/SignInController.java index 6cde88c..d715703 100644 --- a/src/main/java/ui/controller/SignInController.java +++ b/src/main/java/ui/controller/auth/SignInController.java @@ -1,4 +1,4 @@ -package ui.controller; +package ui.controller.auth; import application.user.UserSignIn; import domain.user.User; @@ -7,6 +7,7 @@ import javafx.scene.control.PasswordField; import javafx.scene.control.TextField; import ui.Page; +import ui.controller.NavigationAware; import util.SessionManager; import java.io.IOException; import java.util.function.Consumer; diff --git a/src/main/java/ui/controller/donation/DonationFlowController.java b/src/main/java/ui/controller/donation/DonationFlowController.java index 6b70027..05f7d7e 100644 --- a/src/main/java/ui/controller/donation/DonationFlowController.java +++ b/src/main/java/ui/controller/donation/DonationFlowController.java @@ -55,7 +55,7 @@ public void start(Organization org) throws IOException { // Loads the "not logged in" page private void loadStep1() throws IOException { - FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/DonationNotLoggedIn.fxml")); + FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/donationView/DonationNotLoggedIn.fxml")); Parent root = loader.load(); DonationNotLoggedInController controller = loader.getController(); @@ -67,7 +67,7 @@ private void loadStep1() throws IOException { // Loads the amount selection page private void loadStep2() throws IOException { - FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/DonationAmount.fxml")); + FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/donationView/DonationAmount.fxml")); Parent root = loader.load(); DonationAmountController controller = loader.getController(); @@ -92,7 +92,7 @@ private void loadStep2() throws IOException { // Loads the payment information page private void loadStep3() throws IOException { - FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/DonationPayment.fxml")); + FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/donationView/DonationPayment.fxml")); Parent root = loader.load(); DonationPaymentController controller = loader.getController(); @@ -123,7 +123,7 @@ private void loadStep3() throws IOException { // Loads the confirmation page private void loadStep4() throws IOException { - FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/DonationConfirmation.fxml")); + FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/donationView/DonationConfirmation.fxml")); Parent root = loader.load(); DonationConfirmationController controller = loader.getController(); diff --git a/src/main/java/ui/controller/OrganizationController.java b/src/main/java/ui/controller/organization/OrganizationController.java similarity index 93% rename from src/main/java/ui/controller/OrganizationController.java rename to src/main/java/ui/controller/organization/OrganizationController.java index c868f90..782b1f1 100644 --- a/src/main/java/ui/controller/OrganizationController.java +++ b/src/main/java/ui/controller/organization/OrganizationController.java @@ -1,4 +1,4 @@ -package ui.controller; +package ui.controller.organization; import application.user.UserFavorite; import domain.organization.Organization; @@ -15,6 +15,7 @@ import persistence.dao.FavoriteDao; import persistence.dao.OrganizationDao; import ui.Page; +import ui.controller.NavigationAware; import util.SessionManager; import java.sql.SQLException; @@ -28,7 +29,7 @@ * Controller for the organizations page. * Displays a paginated list of approved organizations with search functionality. */ -public class OrganizationController implements NavigationAware{ +public class OrganizationController implements NavigationAware { @FXML private TextField searchField; @@ -77,10 +78,18 @@ public void setOnShowDetail(BiConsumer onShowDetail) { this.onShowDetail = onShowDetail; } + /** + * Sets the callback to invoke when an unauthenticated user attempts to save an organization. + * @param onSignInRequired the callback to run when sign in is required + */ public void setOnSignInRequired(Runnable onSignInRequired) { this.onSignInRequired = onSignInRequired; } + /** + * Initializes the controller by loading all approved organizations, + * setting up search functionality and responsive card layout. + */ public void initialize() { try { OrganizationDao dao = new OrganizationDao(); diff --git a/src/main/java/ui/controller/OrganizationDetailController.java b/src/main/java/ui/controller/organization/OrganizationDetailController.java similarity index 85% rename from src/main/java/ui/controller/OrganizationDetailController.java rename to src/main/java/ui/controller/organization/OrganizationDetailController.java index 0ab1896..a1d441f 100644 --- a/src/main/java/ui/controller/OrganizationDetailController.java +++ b/src/main/java/ui/controller/organization/OrganizationDetailController.java @@ -1,4 +1,4 @@ -package ui.controller; +package ui.controller.organization; import application.user.UserFavorite; import domain.organization.Organization; @@ -105,16 +105,25 @@ public void setOnClose(Runnable onClose) { this.onClose = onClose; } + /** + * Closes the organization detail popup. + */ @FXML public void handleClose() { if (onClose != null) onClose.run(); } + /** + * Triggers the donate callback for the current organization. + */ @FXML public void handleDonate() { if (onDonate != null) onDonate.accept(organization); } + /** + * Opens the organization's website in the default browser. + */ @FXML public void handleVisitWebsite() { try { @@ -124,11 +133,18 @@ public void handleVisitWebsite() { } } + /** + * Opens the organization's website to read more about it. + */ @FXML public void handleReadMore() { handleVisitWebsite(); } + /** + * Toggles the favorite status of the current organization for the signed-in user. + * If the user is not signed in, the sign-in callback is triggered instead. + */ @FXML public void handleSave() { User user = SessionManager.getSignedInUser(); @@ -147,9 +163,18 @@ public void handleSave() { } } + /** + * Sets the callback to invoke when the user's favorite status changes. + * @param onFavoriteChanged the callback with {@code true} if saved, {@code false} if removed + */ public void setOnFavoriteChanged(Consumer onFavoriteChanged) { this.onFavoriteChanged = onFavoriteChanged; } + + /** + * Sets the callback to invoke when sign in is required. + * @param onSignIn the callback to run when the user is not signed in + */ public void setOnSignIn(Runnable onSignIn) { this.onSignIn = onSignIn; } diff --git a/src/main/java/ui/controller/SignInRequiredController.java b/src/main/java/ui/controller/organization/SignInRequiredController.java similarity index 83% rename from src/main/java/ui/controller/SignInRequiredController.java rename to src/main/java/ui/controller/organization/SignInRequiredController.java index de896b6..3dec275 100644 --- a/src/main/java/ui/controller/SignInRequiredController.java +++ b/src/main/java/ui/controller/organization/SignInRequiredController.java @@ -1,4 +1,4 @@ -package ui.controller; +package ui.controller.organization; import javafx.fxml.FXML; @@ -27,11 +27,17 @@ public void setOnSignIn(Runnable onSignIn) { this.onSignIn = onSignIn; } + /** + * Closes the popup without navigating away. + */ @FXML public void handleCancel() { if (onClose != null) onClose.run(); } + /** + * Closes the popup and triggers the sign in callback. + */ @FXML public void handleSignIn() { if (onSignIn != null) onSignIn.run(); diff --git a/src/main/java/ui/controller/MyProfileController.java b/src/main/java/ui/controller/profile/MyProfileController.java similarity index 90% rename from src/main/java/ui/controller/MyProfileController.java rename to src/main/java/ui/controller/profile/MyProfileController.java index 1915ab9..ba16fbf 100644 --- a/src/main/java/ui/controller/MyProfileController.java +++ b/src/main/java/ui/controller/profile/MyProfileController.java @@ -1,4 +1,4 @@ -package ui.controller; +package ui.controller.profile; import application.user.UserEditProfile; import application.user.UserFavorite; @@ -9,7 +9,6 @@ import java.util.List; import integration.security.Sha256PasswordHasher; -import javafx.application.Platform; import javafx.beans.property.SimpleStringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -29,6 +28,7 @@ import persistence.dao.FavoriteDao; import persistence.dao.UserDao; import ui.Page; +import ui.controller.NavigationAware; import util.SessionManager; import java.util.function.Consumer; @@ -72,10 +72,18 @@ public void setOnNavigate(Consumer onNavigate) { this.onNavigate = onNavigate; } + /** + * Sets the callback to invoke when the user clicks Donate on a saved organization card. + * @param onDonate the callback with the selected organization + */ public void setOnDonate(Consumer onDonate) { this.onDonate = onDonate; } + /** + * Initializes the profile page by loading user information, donation statistics, + * donation history and saved organizations. + */ @FXML public void initialize() { User user = SessionManager.getSignedInUser(); @@ -151,6 +159,9 @@ private VBox createFavoriteCard(Organization org) { return card; } + /** + * Switches the profile view to edit mode, pre-filling fields with current user data. + */ @FXML public void handleEditProfile() { User user = SessionManager.getSignedInUser(); @@ -164,6 +175,10 @@ public void handleEditProfile() { editProfilePane.setManaged(true); } + /** + * Validates and saves the updated profile information. + * Shows an error message if passwords do not match. + */ @FXML public void handleSaveProfile() { String newPassword = newPasswordField.getText(); @@ -189,6 +204,9 @@ public void handleSaveProfile() { handleCancelEdit(); } + /** + * Cancels profile editing and returns to the profile view. + */ @FXML public void handleCancelEdit() { myProfile.setVisible(true); diff --git a/src/main/resources/view/Register.fxml b/src/main/resources/view/auth/Register.fxml similarity index 94% rename from src/main/resources/view/Register.fxml rename to src/main/resources/view/auth/Register.fxml index b73b8a4..e9dc241 100644 --- a/src/main/resources/view/Register.fxml +++ b/src/main/resources/view/auth/Register.fxml @@ -10,7 +10,7 @@ - +
diff --git a/src/main/resources/view/SignIn.fxml b/src/main/resources/view/auth/SignIn.fxml similarity index 94% rename from src/main/resources/view/SignIn.fxml rename to src/main/resources/view/auth/SignIn.fxml index 990bfd7..a7f2d40 100644 --- a/src/main/resources/view/SignIn.fxml +++ b/src/main/resources/view/auth/SignIn.fxml @@ -10,7 +10,7 @@ - +
diff --git a/src/main/resources/view/DonationAmount.fxml b/src/main/resources/view/donationView/DonationAmount.fxml similarity index 89% rename from src/main/resources/view/DonationAmount.fxml rename to src/main/resources/view/donationView/DonationAmount.fxml index 8098a23..b39385b 100644 --- a/src/main/resources/view/DonationAmount.fxml +++ b/src/main/resources/view/donationView/DonationAmount.fxml @@ -9,13 +9,13 @@ - +