From 0eeb8644ebbb777395c13912f11c114457a41893 Mon Sep 17 00:00:00 2001 From: Lucy Ciara Herud-Thomassen <86323303+LucyCiara@users.noreply.github.com> Date: Tue, 21 Apr 2026 13:03:29 +0200 Subject: [PATCH 01/21] update[AuthController]: updated the text of the privacy policy for better transparency --- src/main/java/edu/group5/app/control/AuthController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/edu/group5/app/control/AuthController.java b/src/main/java/edu/group5/app/control/AuthController.java index 7c34afd..11c3288 100644 --- a/src/main/java/edu/group5/app/control/AuthController.java +++ b/src/main/java/edu/group5/app/control/AuthController.java @@ -95,8 +95,9 @@ public void handleSignUp(SignUpPageView view, String firstName, String lastName, privacyPolicy.setHeaderText("Accept Privacy Policy"); privacyPolicy.setContentText( "Your user information like:\n" + - "Name and email—as well as donations tied to your account—will be saved locally on your machine.\n" + - "By creating an account, you accept the right of our app to store this information."); + "Name and email—as well as donations tied to your account—will be saved locally on your machine.\n" + + "This information is only used to create your account, and no data will be sold to third parties.\n" + + "By creating an account, you accept the right of our app to store this information on your computer."); if (privacyPolicy.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.OK) { boolean success = userService.registerUser( From ad840a785c2d404122da84df96b7e5984c8cef21 Mon Sep 17 00:00:00 2001 From: Lucy Ciara Herud-Thomassen <86323303+LucyCiara@users.noreply.github.com> Date: Tue, 21 Apr 2026 13:45:45 +0200 Subject: [PATCH 02/21] fix[UserService]: re-implement email checker for sign-up --- src/main/java/edu/group5/app/model/user/UserService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/group5/app/model/user/UserService.java b/src/main/java/edu/group5/app/model/user/UserService.java index 6bee483..e6439f1 100644 --- a/src/main/java/edu/group5/app/model/user/UserService.java +++ b/src/main/java/edu/group5/app/model/user/UserService.java @@ -51,7 +51,8 @@ public boolean registerUser(String role, String firstName, String lastName, firstName == null || firstName.trim().isEmpty() || lastName == null || lastName.trim().isEmpty() || email == null || email.trim().isEmpty() || - passwordHash == null || passwordHash.trim().isEmpty()) { + passwordHash == null || passwordHash.trim().isEmpty() || + this.getUserByEmail(email) != null) { return false; } User user; From 9aff5fa26214f80a9a8fc11293dfc2bc91737d8c Mon Sep 17 00:00:00 2001 From: Fredrik Marjoni Date: Tue, 21 Apr 2026 13:55:34 +0200 Subject: [PATCH 03/21] Update[JUnit]: add more JUnit tests for better test coverage --- .../java/edu/group5/app/model/AppState.java | 6 +- .../edu/group5/app/model/AppStateTest.java | 69 +++++++++++++++++++ .../model/donation/DonationServiceTest.java | 29 +++++++- .../app/model/donation/DonationTest.java | 12 +++- .../OrganizationRepositoryTest.java | 24 +++++++ .../organization/OrganizationScraperTest.java | 13 ++++ 6 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 src/test/java/edu/group5/app/model/AppStateTest.java diff --git a/src/main/java/edu/group5/app/model/AppState.java b/src/main/java/edu/group5/app/model/AppState.java index bd56ba0..e17bda9 100644 --- a/src/main/java/edu/group5/app/model/AppState.java +++ b/src/main/java/edu/group5/app/model/AppState.java @@ -18,7 +18,7 @@ public class AppState { private User currentUser; private BigDecimal currentDonationAmount; private Organization currentOrganization; - private String currentDonation; + private String currentPaymentMethod; /** * Gets the current user of the application. @@ -73,7 +73,7 @@ public void setCurrentDonationAmount(BigDecimal amount) { * @return the current payment method */ public String getCurrentPaymentMethod() { - return this.currentDonation; + return this.currentPaymentMethod; } /** @@ -81,6 +81,6 @@ public String getCurrentPaymentMethod() { * @param paymentMethod the payment method to set as the current payment method */ public void setCurrentPaymentMethod(String paymentMethod){ - this.currentDonation = paymentMethod; + this.currentPaymentMethod = paymentMethod; } } diff --git a/src/test/java/edu/group5/app/model/AppStateTest.java b/src/test/java/edu/group5/app/model/AppStateTest.java new file mode 100644 index 0000000..1254033 --- /dev/null +++ b/src/test/java/edu/group5/app/model/AppStateTest.java @@ -0,0 +1,69 @@ +package edu.group5.app.model; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.math.BigDecimal; + +import org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import edu.group5.app.model.organization.Organization; +import edu.group5.app.model.user.Customer; +import edu.group5.app.model.user.User; + +public class AppStateTest { + private User nullUser; + private BigDecimal nullDonationAmount; + private Organization nullOrganization; + private String nullPaymentMethod; + + private User currentTestUser; + private BigDecimal currentTestDonationAmount; + private Organization currentTestOrganization; + private String currentTestPaymentMethod; + + @BeforeEach + void setUp() { + nullUser = null; + nullDonationAmount = null; + nullOrganization = null; + nullPaymentMethod = null; + + currentTestUser = new Customer(1, "Bob", + "Builder", "testuser@example.com", + "password123"); + + currentTestDonationAmount = BigDecimal.ZERO; + + currentTestOrganization = new Organization(1738, "TestOrg", + true, "https://testorg.example.com", true, + "A test organization", "https://testorg.example.com/logo.png"); + + currentTestPaymentMethod = "Credit Card"; + } + + @Test + void gettersAndSetters_WorkCorrectly() { + AppState appState = new AppState(); + + // Test current user + assertEquals(nullUser, appState.getCurrentUser()); + appState.setCurrentUser(currentTestUser); + assertEquals(currentTestUser, appState.getCurrentUser()); + + // Test current donation amount + assertEquals(nullDonationAmount, appState.getCurrentDonationAmount()); + appState.setCurrentDonationAmount(currentTestDonationAmount); + assertEquals(currentTestDonationAmount, appState.getCurrentDonationAmount()); + + // Test current organization + assertEquals(nullOrganization, appState.getCurrentOrganization()); + appState.setCurrentOrganization(currentTestOrganization); + assertEquals(currentTestOrganization, appState.getCurrentOrganization()); + + // Test current payment method + assertEquals(nullPaymentMethod, appState.getCurrentPaymentMethod()); + appState.setCurrentPaymentMethod(currentTestPaymentMethod); + assertEquals(currentTestPaymentMethod, appState.getCurrentPaymentMethod()); + } +} diff --git a/src/test/java/edu/group5/app/model/donation/DonationServiceTest.java b/src/test/java/edu/group5/app/model/donation/DonationServiceTest.java index 58bd170..3b1e5ff 100644 --- a/src/test/java/edu/group5/app/model/donation/DonationServiceTest.java +++ b/src/test/java/edu/group5/app/model/donation/DonationServiceTest.java @@ -12,7 +12,7 @@ import java.time.Instant; import java.util.ArrayList; import java.util.HashMap; - +import java.util.Map; import static org.junit.jupiter.api.Assertions.*; @@ -61,6 +61,33 @@ void testConstructorThrowsIfOrganizationRepositoryIsNull() { assertEquals("OrganizationRepository can't be null", exception.getMessage()); } + @Test + void getUserDonationsReturnsEmptyMapIfNoDonations() { + assertTrue(donationService.getUserDonations(customer.getUserId()).isEmpty()); + } + + @Test + void getOrganizationDonationsReturnsMapOfDonations() { + Donation donation1 = new Donation(1, customer.getUserId(), + 101, new BigDecimal("20.00"), Timestamp.from(Instant.now()), "Card"); + Donation donation2 = new Donation(2, customer.getUserId(), + 101, new BigDecimal("30.00"), Timestamp.from(Instant.now()), "PayPal"); + donationRepository.addContent(donation1); + donationRepository.addContent(donation2); + + Map donations = donationService.getOrganizationDonations(101); + assertEquals(2, donations.size()); + assertTrue(donations.containsKey(1)); + assertTrue(donations.containsKey(2)); + assertEquals(donation1, donations.get(1)); + assertEquals(donation2, donations.get(2)); + } + + @Test + void getOrganizationDonationsReturnsEmptyMapIfNoDonations() { + assertTrue(donationService.getOrganizationDonations(1).isEmpty()); + } + @Test void donateReturnsFalseIfCustomerNull() { boolean result = donationService.donate(null, diff --git a/src/test/java/edu/group5/app/model/donation/DonationTest.java b/src/test/java/edu/group5/app/model/donation/DonationTest.java index f8f9069..1de92f9 100644 --- a/src/test/java/edu/group5/app/model/donation/DonationTest.java +++ b/src/test/java/edu/group5/app/model/donation/DonationTest.java @@ -74,11 +74,19 @@ void testIfThrowsExceptionWhenOrganizationIdIsNotPositive() { amount1, date1, paymentMethod1); } @Test - void testIfThrowsExceptionWhenAmountIsNotPositive() { + void testIfThrowsExceptionWhenAmountIsNegative() { expectedMessage = "Amount must be positive and not null"; exceptionTest(donationId1, userId1, organizationId1, - new BigDecimal("0.00"), date1, paymentMethod1); + new BigDecimal("-1.00"), date1, paymentMethod1); } + + @Test + void testIfThrowsExceptionWhenAmountIsNull() { + expectedMessage = "Amount must be positive and not null"; + exceptionTest(donationId1, userId1, organizationId1, + null, date1, paymentMethod1); + } + @Test void testIfThrowsExceptionWhenDateIsNull() { expectedMessage = "Date must not be null"; diff --git a/src/test/java/edu/group5/app/model/organization/OrganizationRepositoryTest.java b/src/test/java/edu/group5/app/model/organization/OrganizationRepositoryTest.java index a5eab05..e6b0cf9 100644 --- a/src/test/java/edu/group5/app/model/organization/OrganizationRepositoryTest.java +++ b/src/test/java/edu/group5/app/model/organization/OrganizationRepositoryTest.java @@ -60,6 +60,30 @@ void constructor_ThrowsWhenContentIsNull() { constructorTest(null, "Input data can't be null"); } + @Test + void constructor_SkipsOrganizationWithMissingOrgNumber() { + Object[] content = new Object[] { + Map.of( + "name", "Good Org", + "status", "approved", + "url", "org.com", + "is_pre_approved", true + ), + Map.of( + "org_number", "999", + "name", "Bad Org", + "status", "approved", + "url", "org.com", + "is_pre_approved", true + ) + }; + + OrganizationRepository repo = new OrganizationRepository(content, scraper); + + assertEquals(1, repo.findByOrgNumber(999) != null ? 1 : 0); + assertNull(repo.findByOrgNumber(1)); + } + @Test void constructor_ThrowsWhenScraperIsNull() { Object[] content = new Object[] { diff --git a/src/test/java/edu/group5/app/model/organization/OrganizationScraperTest.java b/src/test/java/edu/group5/app/model/organization/OrganizationScraperTest.java index c6c318b..8807e61 100644 --- a/src/test/java/edu/group5/app/model/organization/OrganizationScraperTest.java +++ b/src/test/java/edu/group5/app/model/organization/OrganizationScraperTest.java @@ -63,4 +63,17 @@ void fetchLogoUrl_CachesResultOnSecondCall() { // If no exception thrown, cache works assertTrue(true); } + + @Test + void fetchDescription_ReturnsCachedValue() { + OrganizationScraper scraper = new OrganizationScraper(); + + // First call - caches (but makes real request) + String result1 = scraper.fetchDescription("https://example.com"); + + // Second call - should return cached without new request + String result2 = scraper.fetchDescription("https://example.com"); + + assertEquals(result1, result2); // Same object = cached + } } From 4927acac63d746e7e2bdd8177fb762f4efa83cdc Mon Sep 17 00:00:00 2001 From: Fredrik Marjoni Date: Tue, 21 Apr 2026 14:53:28 +0200 Subject: [PATCH 04/21] test[JUnit]: add Mockito dependency and more JUnit tests ensuring greater test coverage --- pom.xml | 6 ++ .../java/edu/group5/app/model/Repository.java | 8 -- .../organization/OrganizationScraper.java | 86 ++++++++++++------ .../organization/OrganizationScraperTest.java | 91 +++++++++++++++++-- .../app/model/user/UserServiceTest.java | 6 +- 5 files changed, 148 insertions(+), 49 deletions(-) diff --git a/pom.xml b/pom.xml index 5bbba22..8091ffe 100644 --- a/pom.xml +++ b/pom.xml @@ -73,6 +73,12 @@ jsoup 1.17.2 + + org.mockito + mockito-inline + 5.2.0 + test + diff --git a/src/main/java/edu/group5/app/model/Repository.java b/src/main/java/edu/group5/app/model/Repository.java index 6dd425e..8b4ec61 100644 --- a/src/main/java/edu/group5/app/model/Repository.java +++ b/src/main/java/edu/group5/app/model/Repository.java @@ -23,12 +23,4 @@ protected Repository(Map content) { ParameterValidator.objectChecker(content, "content"); this.content = content; } - - /** - * Gets the content of the repository. - * @return the content of the repository - */ - public Map getContent() { - return content; - } } diff --git a/src/main/java/edu/group5/app/model/organization/OrganizationScraper.java b/src/main/java/edu/group5/app/model/organization/OrganizationScraper.java index ad93736..beb16aa 100644 --- a/src/main/java/edu/group5/app/model/organization/OrganizationScraper.java +++ b/src/main/java/edu/group5/app/model/organization/OrganizationScraper.java @@ -40,31 +40,10 @@ public String fetchDescription(String pageUrl) { .userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") .timeout(5000).get(); - Element section = doc.selectFirst("section.information"); - if (section != null) { - section.select("div.extra-info").remove(); - section.select("a.read-more").remove(); - - // Extract all

tags and

elements as separate paragraphs - String description = section.select("p, div").stream() - .filter(el -> el.tagName().equals("p") || el.select("p").isEmpty()) - .filter(el -> !el.hasClass("extra-info") && !el.hasClass("logo")) - .map(Element::text) - .map(text -> text.replace("Les mer", "").trim()) - .filter(text -> !text.isBlank()) - .collect(Collectors.joining("\n\n")); - - // Fallback: if no paragraphs found, get all text from section - if (description.isBlank()) { - description = section.text().trim(); - } - description = description.replace("Les mer", "").trim(); - - // Only cache and return if we found something meaningful - if (!description.isBlank()) { - descriptionCache.put(pageUrl, description); - return description; - } + String description = parseDescription(doc); + if (!description.isBlank()) { + descriptionCache.put(pageUrl, description); + return description; } } catch (Exception e) { System.out.println("Could not get description for: " + pageUrl); @@ -72,6 +51,42 @@ public String fetchDescription(String pageUrl) { return null; } + /** + * Parses the description from a Document by extracting text content + * from {@code
}. + * + * @param doc the Document to parse + * @return the description text, or empty string if not found + */ + protected String parseDescription(Document doc) { + Element section = doc.selectFirst("section.information"); + if (section != null) { + section.select("div.extra-info").remove(); + section.select("a.read-more").remove(); + + // Extract all

tags and

elements as separate paragraphs + String description = section.select("p, div").stream() + .filter(el -> el.tagName().equals("p") || el.select("p").isEmpty()) + .filter(el -> !el.hasClass("extra-info") && !el.hasClass("logo")) + .map(Element::text) + .map(text -> text.replace("Les mer", "").trim()) + .filter(text -> !text.isBlank()) + .collect(Collectors.joining("\n\n")); + + // Fallback: if no paragraphs found, get all text from section + if (description.isBlank()) { + description = section.text().trim(); + } + description = description.replace("Les mer", "").trim(); + + // Only return if we found something meaningful + if (!description.isBlank()) { + return description; + } + } + return ""; + } + /** * Fetches the logo URL for the given page by scraping the {@code div.logo img} * element. Results are cached so each URL is only fetched once. @@ -88,10 +103,9 @@ public String fetchLogoUrl(String pageUrl) { Document doc = Jsoup.connect(pageUrl) .userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") .timeout(5000).get(); - Element img = doc.selectFirst("div.logo img"); - if (img != null) { - String logoUrl = img.absUrl("src"); + String logoUrl = parseLogoUrl(doc); + if (!logoUrl.isBlank()) { logoCache.put(pageUrl, logoUrl); return logoUrl; } @@ -100,4 +114,20 @@ public String fetchLogoUrl(String pageUrl) { } return null; } + + /** + * Parses the logo URL from a Document by extracting the image src + * from {@code div.logo img}. + * + * @param doc the Document to parse + * @return the absolute logo URL, or empty string if not found + */ + protected String parseLogoUrl(Document doc) { + Element img = doc.selectFirst("div.logo img"); + if (img != null) { + String logoUrl = img.absUrl("src"); + return logoUrl.isBlank() ? "" : logoUrl; + } + return ""; + } } diff --git a/src/test/java/edu/group5/app/model/organization/OrganizationScraperTest.java b/src/test/java/edu/group5/app/model/organization/OrganizationScraperTest.java index 8807e61..e78c492 100644 --- a/src/test/java/edu/group5/app/model/organization/OrganizationScraperTest.java +++ b/src/test/java/edu/group5/app/model/organization/OrganizationScraperTest.java @@ -2,8 +2,15 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.mockito.MockedStatic; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; class OrganizationScraperTest { @@ -15,23 +22,23 @@ void setUp() { } @Test - void fetchDescription_ReturnsNullWhenUrlIsNull() { + void fetchDescriptionReturnsNullWhenUrlIsNull() { assertNull(scraper.fetchDescription(null)); } @Test - void fetchDescription_ReturnsNullWhenUrlIsBlank() { + void fetchDescriptionReturnsNullWhenUrlIsBlank() { assertNull(scraper.fetchDescription("")); } @Test - void fetchDescription_ReturnsNullWhenUrlIsInvalid() { + void fetchDescriptionReturnsNullWhenUrlIsInvalid() { String result = scraper.fetchDescription("https://invalid-url-that-does-not-exist-xyz123.com"); assertNull(result); } @Test - void fetchDescription_CachesResultOnSecondCall() { + void fetchDescriptionCachesResultOnSecondCall() { // Mock URLs won't work, but cache still works with null returns scraper.fetchDescription("https://example.com"); scraper.fetchDescription("https://example.com"); @@ -40,23 +47,23 @@ void fetchDescription_CachesResultOnSecondCall() { } @Test - void fetchLogoUrl_ReturnsNullWhenUrlIsNull() { + void fetchLogoUrlReturnsNullWhenUrlIsNull() { assertNull(scraper.fetchLogoUrl(null)); } @Test - void fetchLogoUrl_ReturnsNullWhenUrlIsBlank() { + void fetchLogoUrlReturnsNullWhenUrlIsBlank() { assertNull(scraper.fetchLogoUrl("")); } @Test - void fetchLogoUrl_ReturnsNullWhenUrlIsInvalid() { + void fetchLogoUrlReturnsNullWhenUrlIsInvalid() { String result = scraper.fetchLogoUrl("https://invalid-url-that-does-not-exist-xyz123.com"); assertNull(result); } @Test - void fetchLogoUrl_CachesResultOnSecondCall() { + void fetchLogoUrlCachesResultOnSecondCall() { // Mock URLs won't work, but cache still works with null returns scraper.fetchLogoUrl("https://example.com"); scraper.fetchLogoUrl("https://example.com"); @@ -65,7 +72,7 @@ void fetchLogoUrl_CachesResultOnSecondCall() { } @Test - void fetchDescription_ReturnsCachedValue() { + void fetchDescriptionReturnsCachedValue() { OrganizationScraper scraper = new OrganizationScraper(); // First call - caches (but makes real request) @@ -76,4 +83,68 @@ void fetchDescription_ReturnsCachedValue() { assertEquals(result1, result2); // Same object = cached } -} + + @Test + void fetchDescriptionHandlesExceptionGracefully() { + String result = scraper.fetchDescription("https://invalid-domain-xyz.test"); + assertNull(result); + } + + @Test + void fetchLogoUrlHandlesExceptionGracefully() { + String result = scraper.fetchLogoUrl("https://invalid-domain-xyz.test"); + assertNull(result); + } + + @Test + void parseDescriptionExtractsParagraphs() { + String html = "

First paragraph

Second paragraph

"; + Document doc = Jsoup.parse(html); + assertTrue(scraper.parseDescription(doc).contains("First paragraph")); + } + + @Test + void parseDescriptionUsesFallbackWhenNoParagraphs() { + String html = "
Fallback text without paragraph tags
"; + Document doc = Jsoup.parse(html); + assertEquals("Fallback text without paragraph tags", scraper.parseDescription(doc)); + } + + @Test + void parseDescriptionRemovesExtraInfoDivs() { + String html = "

Keep this

Remove this
"; + Document doc = Jsoup.parse(html); + String result = scraper.parseDescription(doc); + assertTrue(result.contains("Keep this")); + assertFalse(result.contains("Remove this")); + } + + @Test + void parseDescriptionFiltersOutLesMer() { + String html = "

Some text Les mer More text

"; + Document doc = Jsoup.parse(html); + assertFalse(scraper.parseDescription(doc).contains("Les mer")); + } + + @Test + void parseDescriptionReturnsEmptyStringWhenNoSection() { + String html = "
No section here
"; + Document doc = Jsoup.parse(html); + assertEquals("", scraper.parseDescription(doc)); + } + + @Test + void parseLogoUrlExtractsImageUrl() { + String html = "
"; + Document doc = Jsoup.parse(html); + doc.setBaseUri("https://example.com"); + assertTrue(scraper.parseLogoUrl(doc).contains("logo.png")); + } + + @Test + void parseLogoUrlReturnsEmptyWhenNoImage() { + String html = "
"; + Document doc = Jsoup.parse(html); + assertEquals("", scraper.parseLogoUrl(doc)); + } +} \ No newline at end of file diff --git a/src/test/java/edu/group5/app/model/user/UserServiceTest.java b/src/test/java/edu/group5/app/model/user/UserServiceTest.java index 104e2f1..459b635 100644 --- a/src/test/java/edu/group5/app/model/user/UserServiceTest.java +++ b/src/test/java/edu/group5/app/model/user/UserServiceTest.java @@ -71,11 +71,11 @@ void registerUserInvalidInputsReturnFalse() { } @Test - void registerUserDuplicateEmailAllowedInCurrentCode() { + void registerUserDuplicateEmailNotAllowedInCurrentCode() { boolean result = service.registerUser("Customer", "John", "Cena", "john.cena@example.com", "$2a$10$hashed"); - assertTrue(result); - assertEquals(3, repo.getUsers().size()); + assertFalse(result); + assertEquals(2, repo.getUsers().size()); } @Test From fe3e366c72f6339b9ccc3af101dc421881cea886 Mon Sep 17 00:00:00 2001 From: Fredrik Marjoni Date: Tue, 21 Apr 2026 14:59:50 +0200 Subject: [PATCH 05/21] fix[OrganizationPageView]: fix picture quality on Organization logo --- .../group5/app/view/organizationpage/OrganizationPageView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/edu/group5/app/view/organizationpage/OrganizationPageView.java b/src/main/java/edu/group5/app/view/organizationpage/OrganizationPageView.java index f591d26..7ea3b6b 100644 --- a/src/main/java/edu/group5/app/view/organizationpage/OrganizationPageView.java +++ b/src/main/java/edu/group5/app/view/organizationpage/OrganizationPageView.java @@ -96,7 +96,7 @@ private StackPane createImageContainer() { // Load image in background thread to avoid blocking UI new Thread(() -> { try { - Image image = new Image(org.logoUrl(), 120, 120, true, true); + Image image = new Image(org.logoUrl(), 350, 350, true, true); Platform.runLater(() -> { ImageView logo = new ImageView(image); logo.setId("logo"); From b317fbd1cc0258ded58108a4400d0e93b4ef8649 Mon Sep 17 00:00:00 2001 From: Fredrik Marjoni Date: Tue, 21 Apr 2026 15:07:09 +0200 Subject: [PATCH 06/21] fix[pom.xml]: remove Mockito dependency due to it being unused in the JUnit tests --- pom.xml | 6 ------ .../app/model/organization/OrganizationScraperTest.java | 6 +----- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 8091ffe..5bbba22 100644 --- a/pom.xml +++ b/pom.xml @@ -73,12 +73,6 @@ jsoup 1.17.2 - - org.mockito - mockito-inline - 5.2.0 - test - diff --git a/src/test/java/edu/group5/app/model/organization/OrganizationScraperTest.java b/src/test/java/edu/group5/app/model/organization/OrganizationScraperTest.java index e78c492..5d48428 100644 --- a/src/test/java/edu/group5/app/model/organization/OrganizationScraperTest.java +++ b/src/test/java/edu/group5/app/model/organization/OrganizationScraperTest.java @@ -4,13 +4,9 @@ import org.junit.jupiter.api.Test; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; -import org.mockito.MockedStatic; + import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.times; class OrganizationScraperTest { From c4955dea787a2947777370af829e639f9e2a42c7 Mon Sep 17 00:00:00 2001 From: Lucy Ciara Herud-Thomassen <86323303+LucyCiara@users.noreply.github.com> Date: Tue, 21 Apr 2026 15:51:22 +0200 Subject: [PATCH 07/21] update[DbWrapper]: add exception throwing for SQLException edgecase for use in testing --- src/main/java/edu/group5/app/model/wrapper/DbWrapper.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/group5/app/model/wrapper/DbWrapper.java b/src/main/java/edu/group5/app/model/wrapper/DbWrapper.java index f357ae1..fd2a2cc 100644 --- a/src/main/java/edu/group5/app/model/wrapper/DbWrapper.java +++ b/src/main/java/edu/group5/app/model/wrapper/DbWrapper.java @@ -230,8 +230,10 @@ private List importDonations(int user_id, boolean all) { * @throws IllegalArgumentException This exception is thrown when data is null, its rows are not * of length 6, any of the rows are null, any of the rows are duplicates or existing rows in * the database, or any of the values in the rows can't be cast to the correct data-types. + * @throws SQLException Is thrown when an unexpected exception like trying to export a number that's + * too big happens. */ - public int exportDonations(List data) throws IllegalArgumentException { + public int exportDonations(List data) throws IllegalArgumentException, SQLException { this.fetchAllDonations(); ParameterValidator.exportChecker(data, "data", this.donations, 6); @@ -263,6 +265,7 @@ public int exportDonations(List data) throws IllegalArgumentException this.logger.info("Donations exported"); } catch (SQLException e) { this.logger.log(Level.SEVERE, "Unexpected SQL exception", e); + throw new SQLException("An unexpected SQL exception has occurred. This might be caused by inserting an item that is too large."); } finally { this.close(null, ps); } From f0430cdec860f44e89ed2feca8273fc3115aa7ae Mon Sep 17 00:00:00 2001 From: Lucy Ciara Herud-Thomassen <86323303+LucyCiara@users.noreply.github.com> Date: Tue, 21 Apr 2026 18:23:18 +0200 Subject: [PATCH 08/21] test[DbWrapper]: add testing for SQLException --- .../model/wrapper/DbWrapperDonationsTest.java | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/test/java/edu/group5/app/model/wrapper/DbWrapperDonationsTest.java b/src/test/java/edu/group5/app/model/wrapper/DbWrapperDonationsTest.java index 1db7a90..f9cc8ff 100644 --- a/src/test/java/edu/group5/app/model/wrapper/DbWrapperDonationsTest.java +++ b/src/test/java/edu/group5/app/model/wrapper/DbWrapperDonationsTest.java @@ -7,6 +7,7 @@ import java.math.BigDecimal; import java.math.RoundingMode; +import java.sql.SQLException; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; @@ -25,12 +26,14 @@ public class DbWrapperDonationsTest { private Object[] cutoffDonation; private Object[] freakyDonation; private Object[] repeatingDonation; + private Object[] tooBigDonation; private List donations; private List donations2; private List donations3; private List repeatedDonations; private List wrongFormatDonations; private List wrongDatatypeDonations; + private List tooBigDonations; private List nullList; private static final int PRECISION = 5; @@ -40,10 +43,10 @@ public class DbWrapperDonationsTest { @BeforeEach void init() { this.db = new DbWrapper(true); - String[] firstNames = new String[] { "John", "Jane", "Cutoff", "Freaky", "Repeating" }; - String[] lastNames = new String[] { "Doe", "Doe", "Joh", "Bill", "JoeJoe" }; + String[] firstNames = new String[] { "John", "Jane", "Cutoff", "Freaky", "Repeating", "Big" }; + String[] lastNames = new String[] { "Doe", "Doe", "Joh", "Bill", "JoeJoe", "Willy" }; this.users = new ArrayList(); - for (int i = 0; i < 5; i++) { + for (int i = 0; i < firstNames.length; i++) { Object[] row = new Object[6]; row[0] = i + 1; row[1] = "Customer"; @@ -84,6 +87,13 @@ void init() { this.wrongDatatypeDonations = new ArrayList(); this.wrongDatatypeDonations.add(freakyDonation); + this.tooBigDonation = new Object[] { + 6, 6, 999999, new BigDecimal("9999999999999999999999999999999"), + new Timestamp(new Date().getTime()), "Azerbaijani technologies" + }; + this.tooBigDonations = new ArrayList(); + this.tooBigDonations.add(tooBigDonation); + Object[] nullRow = new Object[] {null, null, null, null, null, null}; this.nullList = new ArrayList(); this.nullList.add(nullRow); @@ -150,7 +160,7 @@ public void wronglyDatatypedDonationsThrowsExpectedException() { @Test public void addingSameDonationTwiceThrowsExpectedException() { assertTrue(this.db.importDonations((int) this.users.get(0)[0]).size() == 0); - assertEquals(1, this.db.exportDonations(this.donations)); + assertDoesNotThrow(() -> assertEquals(1, this.db.exportDonations(this.donations))); IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { this.db.exportDonations(this.donations); }); @@ -163,7 +173,7 @@ public void addingSameDonationTwiceThrowsExpectedException() { public void addingSameDonationTwiceThrowsExpectedException2() { assertTrue(this.db.importDonations((int) this.users.get(0)[0]).size() == 0); assertTrue(this.db.importDonations((int) this.users.get(1)[0]).size() == 0); - assertEquals(2, this.db.exportDonations(this.donations2)); + assertDoesNotThrow(() -> assertEquals(2, this.db.exportDonations(this.donations2))); IllegalArgumentException exception = assertThrows( IllegalArgumentException.class, () -> { @@ -212,8 +222,20 @@ public void addingDonationListWithNullInRowThrowsExpectedException() { @Test public void dataIsEmptyAfterExportingAndImportingEmptyList() { assertTrue(this.db.importDonations((int) this.users.get(0)[0]).size() == 0); - assertEquals(0, this.db.exportDonations(new ArrayList())); + assertDoesNotThrow(() -> assertEquals(0, this.db.exportDonations(new ArrayList()))); assertTrue(this.db.importDonations((int) this.users.get(0)[0]).size() == 0); assertTrue(this.db.disconnect()); } + + @Test + public void exportTooBigNumberThrowsSQLException() { + SQLException exception = assertThrows(SQLException.class, () -> { + this.db.exportDonations(this.tooBigDonations); + }); + assertEquals( + "An unexpected SQL exception has occurred. " + + "This might be caused by inserting an item that is too large.", + exception.getMessage() + ); + } } From d6016d5cca6ebfb7ab435b6c41ca1deb9eb9c3b4 Mon Sep 17 00:00:00 2001 From: Lucy Ciara Herud-Thomassen <86323303+LucyCiara@users.noreply.github.com> Date: Tue, 21 Apr 2026 18:41:55 +0200 Subject: [PATCH 09/21] update[DbWrapper]: add exception throwing for exportUsers in case of SQLException --- src/main/java/edu/group5/app/model/wrapper/DbWrapper.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/group5/app/model/wrapper/DbWrapper.java b/src/main/java/edu/group5/app/model/wrapper/DbWrapper.java index fd2a2cc..3325b8c 100644 --- a/src/main/java/edu/group5/app/model/wrapper/DbWrapper.java +++ b/src/main/java/edu/group5/app/model/wrapper/DbWrapper.java @@ -132,8 +132,10 @@ public List importUsers() { * @throws IllegalArgumentException This exception is thrown when data is null, its rows are not * of length 6, any of the rows are null, any of the rows are duplicates or existing rows in * the database, or any of the values in the rows can't be cast to the correct data-types. + * @throws SQLException Is thrown when an unexpected exception like trying to export a number + * that's too big happens. */ - public int exportUsers(List data) throws IllegalArgumentException { + public int exportUsers(List data) throws IllegalArgumentException, SQLException { this.importUsers(); ParameterValidator.exportChecker(data, "data", this.users, 6); @@ -163,6 +165,7 @@ public int exportUsers(List data) throws IllegalArgumentException { this.logger.info("Users exported"); } catch (SQLException e) { this.logger.log(Level.SEVERE, "Unexpected SQL exception", e); + throw new SQLException("An unexpected SQL exception has occurred. This might be caused by inserting an item that is too large."); } finally { this.close(null, ps); } From 35f4d49fe2aa902f180ab8c3f61e5bd49bb09f86 Mon Sep 17 00:00:00 2001 From: Lucy Ciara Herud-Thomassen <86323303+LucyCiara@users.noreply.github.com> Date: Tue, 21 Apr 2026 18:46:12 +0200 Subject: [PATCH 10/21] test&update[DbWrapper]: add test for exporting user with too long String and handle exception throwing in DonationsTest --- .../model/wrapper/DbWrapperDonationsTest.java | 6 ++++- .../app/model/wrapper/DbWrapperUserTest.java | 27 +++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/test/java/edu/group5/app/model/wrapper/DbWrapperDonationsTest.java b/src/test/java/edu/group5/app/model/wrapper/DbWrapperDonationsTest.java index f9cc8ff..289b803 100644 --- a/src/test/java/edu/group5/app/model/wrapper/DbWrapperDonationsTest.java +++ b/src/test/java/edu/group5/app/model/wrapper/DbWrapperDonationsTest.java @@ -99,7 +99,11 @@ void init() { this.nullList.add(nullRow); this.db.connect(); - this.db.exportUsers(users); + try { + this.db.exportUsers(users); + } catch (Exception e) { + // This exception won't happen + } } private static boolean donationEquals(Object[] array1, Object[] array2) { diff --git a/src/test/java/edu/group5/app/model/wrapper/DbWrapperUserTest.java b/src/test/java/edu/group5/app/model/wrapper/DbWrapperUserTest.java index 00107de..e8621d8 100644 --- a/src/test/java/edu/group5/app/model/wrapper/DbWrapperUserTest.java +++ b/src/test/java/edu/group5/app/model/wrapper/DbWrapperUserTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -20,12 +21,14 @@ public class DbWrapperUserTest { private Object[] cutoffJoh; private Object[] freakyBill; private Object[] repeatingJoeJoe; + private Object[] bigWilly; private List users; private List users2; private List users3; private List repeatedUsers; private List wrongFormatUsers; private List wrongDatatypeUsers; + private List tooBigUsers; private List nullList; private DbWrapper db; @@ -60,6 +63,12 @@ void init() { this.wrongDatatypeUsers = new ArrayList(); this.wrongDatatypeUsers.add(freakyBill); + this.bigWilly = new Object[] { + 6, "Customer", "Big", "Willy", "bigdwilly@waaaaaytoolargemail.com", "passssssssssssword" + }; + this.tooBigUsers = new ArrayList(); + this.tooBigUsers.add(this.bigWilly); + Object[] nullRow = new Object[] {null, null, null, null, null, null}; this.nullList = new ArrayList(); this.nullList.add(nullRow); @@ -122,7 +131,7 @@ public void wronglyDatatypedUsersThrowsExpectedException() { public void addingSameUserTwiceThrowsExpectedException() { assertTrue(this.db.connect()); assertTrue(this.db.importUsers().size() == 0); - assertEquals(1, this.db.exportUsers(this.users)); + assertDoesNotThrow(() -> assertEquals(1, this.db.exportUsers(this.users))); IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { this.db.exportUsers(this.users); }); @@ -135,7 +144,7 @@ public void addingSameUserTwiceThrowsExpectedException() { public void addingSameUserTwiceThrowsExpectedException2() { assertTrue(this.db.connect()); assertTrue(this.db.importUsers().size() == 0); - assertEquals(2, this.db.exportUsers(this.users2)); + assertDoesNotThrow(() -> assertEquals(2, this.db.exportUsers(this.users2))); IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { this.db.exportUsers(this.users); }); @@ -179,4 +188,18 @@ public void addingUserListWithNullInRowThrowsExpectedException() { assertTrue(this.db.disconnect()); assertEquals("One or more rows in data contains null values", exception.getMessage()); } + + @Test + public void exportTooLongNameThrowsSQLException() { + assertTrue(this.db.connect()); + assertTrue(this.db.importUsers().size() == 0); + SQLException exception = assertThrows(SQLException.class, () -> { + this.db.exportUsers(this.tooBigUsers); + }); + assertEquals( + "An unexpected SQL exception has occurred. " + + "This might be caused by inserting an item that is too large.", + exception.getMessage() + ); + } } From 9fbe278b908494822d40053a31474b99414adc8b Mon Sep 17 00:00:00 2001 From: Lucy Ciara Herud-Thomassen <86323303+LucyCiara@users.noreply.github.com> Date: Tue, 21 Apr 2026 21:38:03 +0200 Subject: [PATCH 11/21] feat[AuthController]: add restrictions for user input that's too long Add checks for user input that's too long and show corresponding error messages in GUI --- .../group5/app/control/AuthController.java | 106 +++++++++++++++--- 1 file changed, 88 insertions(+), 18 deletions(-) diff --git a/src/main/java/edu/group5/app/control/AuthController.java b/src/main/java/edu/group5/app/control/AuthController.java index 11c3288..347a52e 100644 --- a/src/main/java/edu/group5/app/control/AuthController.java +++ b/src/main/java/edu/group5/app/control/AuthController.java @@ -9,6 +9,10 @@ import javafx.scene.control.Alert; import javafx.scene.control.ButtonType; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; /** @@ -74,44 +78,110 @@ public User getCurrentUser() { * @param passwordChars the user's password */ public void handleSignUp(SignUpPageView view, String firstName, String lastName, String email, char[] passwordChars) { - if (firstName == null || firstName.trim().isEmpty() || - lastName == null || lastName.trim().isEmpty() || - email == null || email.trim().isEmpty() || - passwordChars == null || passwordChars.length == 0) { + if (firstName == null || firstName.trim().isEmpty() + || lastName == null || lastName.trim().isEmpty() + || email == null || email.trim().isEmpty() + || passwordChars == null || passwordChars.length == 0) { view.showError("All fields are required"); return; } - BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); - - // Clears password char array after creating a hash. - String hashedPassword = encoder.encode(new String(passwordChars)); - for (int i = 0; i < passwordChars.length; i++) { - passwordChars[i] = '\u0000'; + if (firstName.length() > 32 || lastName.length() > 32 + || email.length() > 32 || passwordChars.length > 72) { + + HashMap> fields = new HashMap>(); + List fields32 = new ArrayList(); + List fields72 = new ArrayList(); + fields.put("32", fields32); + fields.put("72", fields72); + + if (firstName.length() > 32) { + fields32.add("First Name"); + } + if (lastName.length() > 32) { + fields32.add("Last Name"); + } + if (email.length() > 32) { + fields32.add("Email"); + } + if (passwordChars.length > 72) { + fields72.add("Password"); + } + + int length32 = fields.get("32").size(); + int length72 = fields.get("72").size(); + + String string32 = ""; + if (length32 > 0) { + if (length32 > 1) { + for (int i = 0; i < length32; i++) { + if (i == length32 - 1) { + string32 += String.format("and %s", fields.get("32").get(i)); + } else { + string32 += String.format("%s, ", fields.get("32").get(i)); + } + } + string32 = string32 + " must have lengths of 32 characters.\n"; + } else { + string32 = fields.get("32").getFirst() + " must have a length of 32 characters.\n"; + } + } + + String string72 = ""; + if (length72 > 0) { + if (length72 > 1) { + for (int i = 0; i < length72; i++) { + if (i == length72 - 1) { + string72 += String.format("and %s", fields.get("72").get(i)); + } else { + string72 += String.format("%s, ", fields.get("72").get(i)); + } + } + string72 = string72 + " must have lengths of 72 characters.\n"; + } else { + string72 = fields.get("72").getFirst() + + " must have a length of 72 characters.\n"; + } + } + + view.showError(string32 + string72 + "Try again."); + return; } Alert privacyPolicy = new Alert(Alert.AlertType.CONFIRMATION); privacyPolicy.setTitle("Accept Privacy Policy"); privacyPolicy.setHeaderText("Accept Privacy Policy"); privacyPolicy.setContentText( - "Your user information like:\n" + - "Name and email—as well as donations tied to your account—will be saved locally on your machine.\n" + - "This information is only used to create your account, and no data will be sold to third parties.\n" + - "By creating an account, you accept the right of our app to store this information on your computer."); + "Your user information like:\n" + + "Name and email—as well as donations tied to your account—" + + "will be saved locally on your machine.\n" + + "This information is only used to create your account," + + "and no data will be sold to third parties.\n" + + "By creating an account," + + "you accept the right of our app to store this information on your computer."); + + BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); + // Clears password char array after creating a hash. + String hashedPassword = encoder.encode(new String(passwordChars)); + for (int i = 0; i < passwordChars.length; i++) { + passwordChars[i] = '\u0000'; + } if (privacyPolicy.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.OK) { boolean success = userService.registerUser( "Customer", firstName, lastName, email, hashedPassword); if (success) { + User user = userService.getUserByEmail(email); appState.setCurrentUser(user); - nav.showHomePage(); - } else { - view.showError("Registration failed. Email may already be in use."); + nav.showHomePage(); + } else { + view.showError("Registration failed. Email may already be in use."); + } } } -} + /** * Handles the login of a {@link User}. From 58d80d9fd90864f89cfe8f72818d82bfe2b1d549 Mon Sep 17 00:00:00 2001 From: Lucy Ciara Herud-Thomassen <86323303+LucyCiara@users.noreply.github.com> Date: Tue, 21 Apr 2026 21:39:36 +0200 Subject: [PATCH 12/21] fix[SignUpPageView]: add text wrapping to prevent longer error messages from being cut off --- src/main/java/edu/group5/app/view/loginpage/SignUpPageView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/edu/group5/app/view/loginpage/SignUpPageView.java b/src/main/java/edu/group5/app/view/loginpage/SignUpPageView.java index bdcfb54..70f40ea 100644 --- a/src/main/java/edu/group5/app/view/loginpage/SignUpPageView.java +++ b/src/main/java/edu/group5/app/view/loginpage/SignUpPageView.java @@ -66,6 +66,7 @@ public char[] getPassword() { public void showError(String message) { errorLabel.setText(message); + errorLabel.setWrapText(true); errorLabel.setStyle("-fx-text-fill: red;"); } @@ -87,7 +88,6 @@ private VBox getSignUpBox() { private Label getErrorLabel() { errorLabel = new Label(); - errorLabel.setPrefHeight(20); return errorLabel; } From f22ad07c693b102e8d54bceb7251c2c94d098074 Mon Sep 17 00:00:00 2001 From: Lucy Ciara Herud-Thomassen <86323303+LucyCiara@users.noreply.github.com> Date: Tue, 21 Apr 2026 23:46:16 +0200 Subject: [PATCH 13/21] feat[DonationController]: prevent donations that are too large or with too many decimals Add error message when trying to donate an amount of money that has too many digits or decimals. --- src/main/java/edu/group5/app/control/AuthController.java | 2 ++ .../java/edu/group5/app/control/DonationController.java | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/main/java/edu/group5/app/control/AuthController.java b/src/main/java/edu/group5/app/control/AuthController.java index 347a52e..d3d4ebe 100644 --- a/src/main/java/edu/group5/app/control/AuthController.java +++ b/src/main/java/edu/group5/app/control/AuthController.java @@ -86,6 +86,7 @@ public void handleSignUp(SignUpPageView view, String firstName, String lastName, return; } + // Checks if any input is too long. if (firstName.length() > 32 || lastName.length() > 32 || email.length() > 32 || passwordChars.length > 72) { @@ -148,6 +149,7 @@ public void handleSignUp(SignUpPageView view, String firstName, String lastName, return; } + // Privacy policy pop-up. Alert privacyPolicy = new Alert(Alert.AlertType.CONFIRMATION); privacyPolicy.setTitle("Accept Privacy Policy"); privacyPolicy.setHeaderText("Accept Privacy Policy"); diff --git a/src/main/java/edu/group5/app/control/DonationController.java b/src/main/java/edu/group5/app/control/DonationController.java index 07af6fd..3ca392d 100644 --- a/src/main/java/edu/group5/app/control/DonationController.java +++ b/src/main/java/edu/group5/app/control/DonationController.java @@ -178,6 +178,12 @@ private void handleDonate() { return; } + // Prevents donations that are too complex from being made + if (amount.stripTrailingZeros().precision() > 32 || amount.stripTrailingZeros().scale() > 16) { + this.showError("The number is too complex, please donate a smaller or less precise number"); + return; + } + // Create donation via service boolean success = service.donate( customer, From 17470d8e2b4c0e7f0a676c293423043603375a4e Mon Sep 17 00:00:00 2001 From: Fredrik Marjoni Date: Thu, 23 Apr 2026 12:41:06 +0200 Subject: [PATCH 14/21] fix[readme]: change interface to abstract when elaborating project structure --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a15b28c..45d2bd0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Help-Me-Help - IDATT1005 Team 5 Portfolio Project Spring 2026 :octocat: + **TEAM 5 STUDENT NAMES**
@@ -61,8 +62,8 @@ src/main/java/edu/group5/app/ │ │ ├── DbWrapper.java (H2 database connection & operations) │ │ └── OrgApiWrapper.java (Innsamlingskontrollen API client) │ ├── AppState.java (Global application state) -│ ├── Repository.java (Base repository interface) -│ └── DBRepository.java (Database repository interface) +│ ├── Repository.java (Base repository abstract) +│ └── DBRepository.java (Database repository abstract) | ├── view/ (JavaFX UI components) │ ├── loginpage/ @@ -108,7 +109,6 @@ src/main/resources/ (Static assets - CSS, images, └── donationpage.css (Donation flow styling) - ### 📦 Package Responsibilities #### Models: Business logic and data entities @@ -178,7 +178,7 @@ The project uses the standard Maven directory structure, which ensures: ## Link to repository📚 -https://git.ntnu.no/Group-5/Help-Me-Help + ## How to run the project📝 @@ -207,6 +207,7 @@ https://git.ntnu.no/Group-5/Help-Me-Help ```bash mvn javafx:run ``` + ``` @@ -216,6 +217,7 @@ https://git.ntnu.no/Group-5/Help-Me-Help Go to repository and download the JAR. 2. Run the JAR in Terminal: + ```bash java --module-path "path\to\javafx\sdk" --add-modules javafx.controls -jar path\to\jar.jar @@ -274,7 +276,6 @@ Each report includes: 3. Execution times 4. Running tests in an IDE - ## References 🔗 For more references and project details, kindly refer yourself to the project report and Wiki pages From 02d4379375b9f92cf2f7afa690f8fb8b337b1e68 Mon Sep 17 00:00:00 2001 From: Fredrik Marjoni Date: Thu, 23 Apr 2026 12:43:24 +0200 Subject: [PATCH 15/21] fix[readme]: fix markdown styling in readme --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index 45d2bd0..ac54a61 100644 --- a/README.md +++ b/README.md @@ -208,10 +208,7 @@ The project uses the standard Maven directory structure, which ensures: mvn javafx:run ``` - ``` - - -**Run From JAR: (Windows) +**Run From JAR: (Windows)** 1. Download Project JAR: Go to repository and download the JAR. From c8f7beb8ca9c37bf1ea13f32cc7f69a5fb0e4311 Mon Sep 17 00:00:00 2001 From: MatheaGjerde Date: Thu, 23 Apr 2026 17:10:09 +0200 Subject: [PATCH 16/21] docs: added javadoc to UserPageView --- .../edu/group5/app/view/userpage/UserPageView.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/group5/app/view/userpage/UserPageView.java b/src/main/java/edu/group5/app/view/userpage/UserPageView.java index cf6f801..aff5b32 100644 --- a/src/main/java/edu/group5/app/view/userpage/UserPageView.java +++ b/src/main/java/edu/group5/app/view/userpage/UserPageView.java @@ -23,7 +23,18 @@ import java.text.SimpleDateFormat; import java.util.*; - +/** + * A view for the user profile page. + * + *

Displays the current user's profile information, + * including avatar, full name, email and location, along with a logout button. + * The page is divided into three sections: + * Profile information, Supported causes, and previous donations.

+ * + *

Supported causes shows all the organizations that the user has donated to. + * Previous donations shows a list of all the user's donations, + * and includes a search bar to filter through them.

+ */ public class UserPageView extends BorderPane { private final NavigationController nav; private final AuthController authController; From a09f2d2cfb125a536786f4118d38d36528a04cae Mon Sep 17 00:00:00 2001 From: MatheaGjerde Date: Thu, 23 Apr 2026 17:11:09 +0200 Subject: [PATCH 17/21] docs: fixed parameter name in javadoc in OrganizationController --- .../java/edu/group5/app/control/OrganizationController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/edu/group5/app/control/OrganizationController.java b/src/main/java/edu/group5/app/control/OrganizationController.java index 6ffe830..49201aa 100644 --- a/src/main/java/edu/group5/app/control/OrganizationController.java +++ b/src/main/java/edu/group5/app/control/OrganizationController.java @@ -33,7 +33,7 @@ public OrganizationController(AppState appState, OrganizationService service) { /** * Sets the current selected organization. - * @param organization the organization to set as current + * @param org the organization to set as current */ public void setCurrentOrganization(Organization org) { appState.setCurrentOrganization(org); From d8098885f0ecf6f9478814028596ae84f462bf04 Mon Sep 17 00:00:00 2001 From: MatheaGjerde Date: Thu, 23 Apr 2026 21:21:06 +0200 Subject: [PATCH 18/21] docs: changed parameter name in javadoc --- src/main/java/edu/group5/app/model/user/UserRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/edu/group5/app/model/user/UserRepository.java b/src/main/java/edu/group5/app/model/user/UserRepository.java index 07f9ca8..ea63bb8 100644 --- a/src/main/java/edu/group5/app/model/user/UserRepository.java +++ b/src/main/java/edu/group5/app/model/user/UserRepository.java @@ -14,7 +14,7 @@ public class UserRepository extends DBRepository { * Constructs UserRepository using Hashmap, * and extends the content from DBRepository. * - * @param content the underlying map used to store users, + * @param rows the underlying map used to store users, * where the key represents the user ID */ public UserRepository(List rows) { From 2330f3ad4c47bbacbe23ccc2c39af852c3754b93 Mon Sep 17 00:00:00 2001 From: Fredrik Marjoni Date: Thu, 23 Apr 2026 22:10:07 +0200 Subject: [PATCH 19/21] chore[user]: Add JavaDoc to UserRepository --- .../edu/group5/app/model/user/UserRepository.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/group5/app/model/user/UserRepository.java b/src/main/java/edu/group5/app/model/user/UserRepository.java index ea63bb8..892b11c 100644 --- a/src/main/java/edu/group5/app/model/user/UserRepository.java +++ b/src/main/java/edu/group5/app/model/user/UserRepository.java @@ -6,7 +6,11 @@ import edu.group5.app.model.DBRepository; import edu.group5.app.utils.ParameterValidator; - +/** + * Repository class for managing User entities. It provides methods to retrieve users, + * find users by their unique identifier or email address, and initializes the repository with input data. + * The repository uses a HashMap to store User objects for efficient retrieval based on their unique identifier. + */ public class UserRepository extends DBRepository { public final static String ROLE_CUSTOMER = "Customer"; @@ -69,6 +73,11 @@ public List export() { } + /** + * Retrieves a copy of the current users in the repository. + * @return a HashMap containing the current users, + * where the key is the user ID and the value is the User object + */ public HashMap getUsers() { return new HashMap<>(content); } From 75915e5614a3b92bb4a662817c139b2a979b3407 Mon Sep 17 00:00:00 2001 From: Fredrik Marjoni Date: Thu, 23 Apr 2026 22:18:07 +0200 Subject: [PATCH 20/21] chore[wrapper]: add more proper JavaDoc to the Wrapper class description --- src/main/java/edu/group5/app/model/wrapper/DbWrapper.java | 4 +++- src/main/java/edu/group5/app/model/wrapper/OrgApiWrapper.java | 3 ++- src/main/java/edu/group5/app/model/wrapper/Wrapper.java | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/edu/group5/app/model/wrapper/DbWrapper.java b/src/main/java/edu/group5/app/model/wrapper/DbWrapper.java index 3325b8c..d0239ae 100644 --- a/src/main/java/edu/group5/app/model/wrapper/DbWrapper.java +++ b/src/main/java/edu/group5/app/model/wrapper/DbWrapper.java @@ -14,7 +14,9 @@ import java.util.logging.Logger; /** - * A class for wrapping the database. + * A class for wrapping the database. It provides methods for connecting and disconnecting to the database, + * importing and exporting users and donations, and handling SQL exceptions. + * The class uses a logger to log important events and exceptions that occur during database operations. */ public class DbWrapper { protected Connection connection; diff --git a/src/main/java/edu/group5/app/model/wrapper/OrgApiWrapper.java b/src/main/java/edu/group5/app/model/wrapper/OrgApiWrapper.java index 2a493e4..d2ebb1a 100644 --- a/src/main/java/edu/group5/app/model/wrapper/OrgApiWrapper.java +++ b/src/main/java/edu/group5/app/model/wrapper/OrgApiWrapper.java @@ -10,7 +10,8 @@ import tools.jackson.databind.ObjectMapper; /** - * A Class for Wrapping an API. + * A Class for Wrapping an API. + * It provides methods for importing data from the API and accessing the imported data. */ public class OrgApiWrapper extends Wrapper { private Object[] data; diff --git a/src/main/java/edu/group5/app/model/wrapper/Wrapper.java b/src/main/java/edu/group5/app/model/wrapper/Wrapper.java index 85d8dc8..cc54c29 100644 --- a/src/main/java/edu/group5/app/model/wrapper/Wrapper.java +++ b/src/main/java/edu/group5/app/model/wrapper/Wrapper.java @@ -2,6 +2,9 @@ /** * An abstract class for all Wrappers of datasets. + * This class defines the structure for dataset wrappers, which are responsible for importing data from various sources + * and providing access to the imported data. Each wrapper must implement the importData method to handle the specific + * data import logic and the getData method to return the imported data in a suitable format. */ abstract class Wrapper { From 46addb5ba6b4377750ed66a8ccdaeb8c0ff4c2e9 Mon Sep 17 00:00:00 2001 From: Fredrik Marjoni Date: Thu, 23 Apr 2026 22:46:19 +0200 Subject: [PATCH 21/21] chore[user]: format JavaDoc to be more compact --- .../java/edu/group5/app/model/user/UserRepository.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/edu/group5/app/model/user/UserRepository.java b/src/main/java/edu/group5/app/model/user/UserRepository.java index 892b11c..aec3e9d 100644 --- a/src/main/java/edu/group5/app/model/user/UserRepository.java +++ b/src/main/java/edu/group5/app/model/user/UserRepository.java @@ -7,9 +7,11 @@ import edu.group5.app.model.DBRepository; import edu.group5.app.utils.ParameterValidator; /** - * Repository class for managing User entities. It provides methods to retrieve users, - * find users by their unique identifier or email address, and initializes the repository with input data. - * The repository uses a HashMap to store User objects for efficient retrieval based on their unique identifier. + * Repository class for managing User entities. + * It provides methods to retrieve users, find users by their unique identifier + * or email address, and initializes the repository with input data. + * The repository uses a HashMap to store + * User objects for efficient retrieval based on their unique identifier. */ public class UserRepository extends DBRepository { public final static String ROLE_CUSTOMER = "Customer";