From e4b38a57edefd99050b4aaf42e9f68552ef57565 Mon Sep 17 00:00:00 2001
From: Fredrik Marjoni
Date: Sat, 28 Feb 2026 11:29:39 +0100
Subject: [PATCH 01/17] perf[User]: Improve User validation and update
corresponding JUnit tests
---
.../edu/group5/app/model/user/UserTest.java | 166 ++++++++++--------
1 file changed, 92 insertions(+), 74 deletions(-)
diff --git a/src/test/java/edu/group5/app/model/user/UserTest.java b/src/test/java/edu/group5/app/model/user/UserTest.java
index 1e7277a..324e3a0 100644
--- a/src/test/java/edu/group5/app/model/user/UserTest.java
+++ b/src/test/java/edu/group5/app/model/user/UserTest.java
@@ -1,136 +1,154 @@
package edu.group5.app.model.user;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class UserTest {
- private static final int TEST_USER_ID = 1;
- private static final String TEST_ROLE = "Donor";
- private static final String TEST_FIRST_NAME = "John";
- private static final String TEST_LAST_NAME = "Doe";
- private static final String TEST_EMAIL = "john.doe@example.com";
- private static final String TEST_PASSWORD = "password123";
- private static final String TEST_PASSWORD_HASH = new BCryptPasswordEncoder().encode(TEST_PASSWORD);
- private static final int WRONG_USER_ID = -5;
- private static final String WRONG_ROLE = "";
- private static final String WRONG_FIRST_NAME = "";
- private static final String WRONG_LAST_NAME = "";
- private static final String WRONG_EMAIL = "";
- private static final String WRONG_PASSWORD_HASH = "";
+ private int testUserId;
+ private String testRole;
+ private String testFirstName;
+ private String testLastName;
+ private String testEmail;
+ private String testPassword;
+ private String testPasswordHash;
- private void constructorTest(int userId, String role, String firstName, String lastName, String email, String passwordHash, boolean negativeTest) {
- boolean exceptionThrown = negativeTest;
- try {
- new User(userId, role, firstName, lastName, email, passwordHash);
- } catch (Exception e) {
- exceptionThrown = !negativeTest;
- } finally {
- assertFalse(exceptionThrown);
- }
+ @BeforeEach
+ void setUp() {
+ testUserId = 1;
+ testRole = "Donor";
+ testFirstName = "John";
+ testLastName = "Doe";
+ testEmail = "john.doe@example.com";
+ testPassword = "password123";
+ testPasswordHash = new BCryptPasswordEncoder().encode(testPassword);
}
- @Test
- public void constructorThrowsNoException() {
- constructorTest(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH, false);
- }
+ private void constructorTest(int userId, String role, String firstName,
+ String lastName, String email, String passwordHash,
+ String expectedMessage) {
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class,
+ () -> new User(userId, role, firstName, lastName, email, passwordHash)
+ );
- @Test
- public void constructorWithNegativeUserIdThrowsException() {
- constructorTest(WRONG_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH, true);
+ assertEquals(expectedMessage, exception.getMessage());
}
@Test
- public void constructorWithEmptyRoleThrowsException() {
- constructorTest(TEST_USER_ID, WRONG_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH, true);
+ void constructorCreatesValidUser() {
+ User user = new User(testUserId, testRole, testFirstName,
+ testLastName, testEmail, testPasswordHash);
+ assertEquals(testUserId, user.getUserId());
+ assertEquals(testRole, user.getRole());
+ assertEquals(testFirstName, user.getFirstName());
+ assertEquals(testLastName, user.getLastName());
+ assertEquals(testEmail, user.getEmail());
+ assertEquals(testPasswordHash, user.getPasswordHash());
}
@Test
- public void constructorWithEmptyFirstNameThrowsException() {
- constructorTest(TEST_USER_ID, TEST_ROLE, WRONG_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH, true);
+ void constructorWithNegativeUserIdThrowsException() {
+ constructorTest(-1, testRole, testFirstName, testLastName,
+ testEmail, testPasswordHash, "User ID must be positive");
}
@Test
- public void constructorWithEmptyLastNameThrowsException() {
- constructorTest(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, WRONG_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH, true);
+ void constructorWithNullRoleThrowsException() {
+ constructorTest(testUserId, null, testFirstName, testLastName,
+ testEmail, testPasswordHash, "Role cannot be null or empty");
}
@Test
- public void constructorWithEmptyEmailThrowsException() {
- constructorTest(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, WRONG_EMAIL, TEST_PASSWORD_HASH, true);
+ void constructorWithEmptyRoleThrowsException() {
+ constructorTest(testUserId, "", testFirstName, testLastName,
+ testEmail, testPasswordHash, "Role cannot be null or empty");
}
@Test
- public void constructorWithEmptyPasswordHashThrowsException() {
- constructorTest(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, WRONG_PASSWORD_HASH, true);
+ void constructorWithNullFirstNameThrowsException() {
+ constructorTest(testUserId, testRole, null, testLastName,
+ testEmail, testPasswordHash, "First name cannot be null or empty");
}
- private void getMethodComparer(User user, boolean negativeTest) {
- if (user.getUserId() == TEST_USER_ID &&
- user.getRole().equals(TEST_ROLE) &&
- user.getFirstName().equals(TEST_FIRST_NAME) &&
- user.getLastName().equals(TEST_LAST_NAME) &&
- user.getEmail().equals(TEST_EMAIL) &&
- user.getPasswordHash() != null) {
- assertFalse(negativeTest);
- }
+ @Test
+ void constructorWithEmptyFirstNameThrowsException() {
+ constructorTest(testUserId, testRole, "", testLastName,
+ testEmail, testPasswordHash, "First name cannot be null or empty");
}
@Test
- public void getMethodsReturnCorrectInformation() {
- User testUser = new User(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH);
- getMethodComparer(testUser, false);
+ void constructorWithNullLastNameThrowsException() {
+ constructorTest(testUserId, testRole, testFirstName, null,
+ testEmail, testPasswordHash, "Last name cannot be null or empty");
}
@Test
- public void verifyPasswordReturnsTrueForCorrectPassword() {
- User testUser = new User(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH);
- assertTrue(testUser.verifyPassword(TEST_PASSWORD));
+ void constructorWithEmptyLastNameThrowsException() {
+ constructorTest(testUserId, testRole, testFirstName, "",
+ testEmail, testPasswordHash, "Last name cannot be null or empty");
}
@Test
- public void verifyPasswordReturnsFalseForIncorrectPassword() {
- User testUser = new User(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH);
- assertFalse(testUser.verifyPassword("wrongPassword"));
+ void constructorWithNullEmailThrowsException() {
+ constructorTest(testUserId, testRole, testFirstName, testLastName,
+ null, testPasswordHash, "Email cannot be null or empty");
}
@Test
- public void verifyPasswordReturnsFalseForNullPassword() {
- User testUser = new User(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH);
- assertFalse(testUser.verifyPassword(null));
+ void constructorWithEmptyEmailThrowsException() {
+ constructorTest(testUserId, testRole, testFirstName, testLastName,
+ "", testPasswordHash, "Email cannot be null or empty");
}
@Test
- public void verifyPasswordReturnsFalseForEmptyPassword() {
- User testUser = new User(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH);
- assertFalse(testUser.verifyPassword(""));
+ void constructorWithNullPasswordHashThrowsException() {
+ constructorTest(testUserId, testRole, testFirstName, testLastName,
+ testEmail, null, "Password hash cannot be null or empty");
}
@Test
- public void constructorWithNullRoleThrowsException() {
- constructorTest(TEST_USER_ID, null, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH, true);
+ void constructorWithEmptyPasswordHashThrowsException() {
+ constructorTest(testUserId, testRole, testFirstName, testLastName,
+ testEmail, "", "Password hash cannot be null or empty");
}
+
@Test
- public void constructorWithNullFirstNameThrowsException() {
- constructorTest(TEST_USER_ID, TEST_ROLE, null, TEST_LAST_NAME, TEST_EMAIL, TEST_PASSWORD_HASH, true);
+ void verifyPasswordReturnsTrueForCorrectPassword() {
+ User user = new User(testUserId, testRole, testFirstName,
+ testLastName, testEmail, testPasswordHash);
+
+ assertTrue(user.verifyPassword(testPassword));
}
@Test
- public void constructorWithNullLastNameThrowsException() {
- constructorTest(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, null, TEST_EMAIL, TEST_PASSWORD_HASH, true);
+ void verifyPasswordReturnsFalseForIncorrectPassword() {
+ User user = new User(testUserId, testRole, testFirstName,
+ testLastName, testEmail, testPasswordHash);
+
+ assertFalse(user.verifyPassword("wrongPassword"));
}
@Test
- public void constructorWithNullEmailThrowsException() {
- constructorTest(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, null, TEST_PASSWORD_HASH, true);
+ void verifyPasswordReturnsFalseForNullPassword() {
+ User user = new User(testUserId, testRole, testFirstName,
+ testLastName, testEmail, testPasswordHash);
+
+ assertFalse(user.verifyPassword(null));
}
@Test
- public void constructorWithNullPasswordHashThrowsException() {
- constructorTest(TEST_USER_ID, TEST_ROLE, TEST_FIRST_NAME, TEST_LAST_NAME, TEST_EMAIL, null, true);
+ void verifyPasswordReturnsFalseForEmptyPassword() {
+ User user = new User(testUserId, testRole, testFirstName,
+ testLastName, testEmail, testPasswordHash);
+
+ assertFalse(user.verifyPassword(""));
}
-}
+}
\ No newline at end of file
From 0db6023cb41551536298c4362ea1eb6e492fc27f Mon Sep 17 00:00:00 2001
From: Fredrik Marjoni
Date: Mon, 2 Mar 2026 15:54:04 +0100
Subject: [PATCH 02/17] Perf: Update model classes and write Customer class
---
.../app/model/donation/DonationService.java | 41 +++++++++++++
.../edu/group5/app/model/user/Customer.java | 49 +++++++++++++++
.../java/edu/group5/app/model/user/User.java | 2 +-
.../user/{UserTest.java => CustomerTest.java} | 59 ++++++++-----------
4 files changed, 116 insertions(+), 35 deletions(-)
create mode 100644 src/main/java/edu/group5/app/model/donation/DonationService.java
create mode 100644 src/main/java/edu/group5/app/model/user/Customer.java
rename src/test/java/edu/group5/app/model/user/{UserTest.java => CustomerTest.java} (67%)
diff --git a/src/main/java/edu/group5/app/model/donation/DonationService.java b/src/main/java/edu/group5/app/model/donation/DonationService.java
new file mode 100644
index 0000000..ecd13da
--- /dev/null
+++ b/src/main/java/edu/group5/app/model/donation/DonationService.java
@@ -0,0 +1,41 @@
+package edu.group5.app.service;
+
+import java.math.BigDecimal;
+import java.sql.Timestamp;
+import edu.group5.app.model.donation.Donation;
+import edu.group5.app.model.organization.Organization;
+import edu.group5.app.model.organization.OrganizationRepository;
+import edu.group5.app.model.user.Customer;
+import edu.group5.app.model.donation.DonationRepository;
+
+public class DonationService {
+
+ private final DonationRepository donationRepository;
+ private final OrganizationRepository organizationRepository;
+
+ public DonationService(DonationRepository donationRepository,
+ OrganizationRepository organizationRepository) {
+ this.donationRepository = donationRepository;
+ this.organizationRepository = organizationRepository;
+ }
+
+ public boolean donate(Customer customer,
+ int orgNumber,
+ BigDecimal amount) {
+ if (customer == null || amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
+ return false;
+ }
+ Organization org = organizationRepository.findByOrgNumber(orgNumber);
+ if (org == null) {
+ return false;
+ }
+ Donation donation = new Donation(
+ customer.getUserId(),
+ org.getOrgNumber(),
+ amount,
+ new Timestamp(System.currentTimeMillis())
+ );
+ donationRepository.addDonation(donation);
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/edu/group5/app/model/user/Customer.java b/src/main/java/edu/group5/app/model/user/Customer.java
new file mode 100644
index 0000000..5d61aab
--- /dev/null
+++ b/src/main/java/edu/group5/app/model/user/Customer.java
@@ -0,0 +1,49 @@
+package edu.group5.app.model.user;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import edu.group5.app.model.organization.Organization;
+
+/**
+ * Customer class represents a customer in the system.
+ * It extends the User class and includes additional functionality specific to customers,
+ * such as managing their preferences for organizations.
+ * Each customer has a map of preferences that associates organization numbers with the corresponding Organization objects.
+ */
+public class Customer extends User {
+ private List preferences;
+
+ /**
+ * Constructs a Customer object.
+ * Calls the {@link User} superclass constructor to initialize common user fields
+ * and initializes the preferences list.
+ * @param userId the unique identifier for the user, must be a positive integer
+ * @param firstName the first name of the customer
+ * @param lastName the last name of the customer
+ * @param email the email address of the customer
+ * @param passwordHash the hashed password of the customer
+ * @throws IllegalArgumentException if any required field is invalid
+ */
+ public Customer(int userId,
+ String firstName,
+ String lastName,
+ String email,
+ String passwordHash) {
+
+ super(userId, "Customer", firstName, lastName, email, passwordHash);
+ this.preferences = new ArrayList<>();
+ }
+
+ public List getPreferences() {
+ return preferences;
+ }
+
+ public void addPreference(int orgNumber) {
+ preferences.add(orgNumber);
+ }
+
+ public void removePreference(int orgNumber) {
+ preferences.remove(Integer.valueOf(orgNumber));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/edu/group5/app/model/user/User.java b/src/main/java/edu/group5/app/model/user/User.java
index 8dd48cd..e20e4f4 100644
--- a/src/main/java/edu/group5/app/model/user/User.java
+++ b/src/main/java/edu/group5/app/model/user/User.java
@@ -9,7 +9,7 @@
* by comparing the provided plaintext password with the stored hashed password using BCrypt.
*
*/
-public class User {
+public abstract class User {
private int userId;
private String role;
private String firstName;
diff --git a/src/test/java/edu/group5/app/model/user/UserTest.java b/src/test/java/edu/group5/app/model/user/CustomerTest.java
similarity index 67%
rename from src/test/java/edu/group5/app/model/user/UserTest.java
rename to src/test/java/edu/group5/app/model/user/CustomerTest.java
index 324e3a0..8678fc9 100644
--- a/src/test/java/edu/group5/app/model/user/UserTest.java
+++ b/src/test/java/edu/group5/app/model/user/CustomerTest.java
@@ -1,5 +1,4 @@
package edu.group5.app.model.user;
-
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -9,10 +8,9 @@
import org.junit.jupiter.api.Test;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
-public class UserTest {
+public class CustomerTest {
private int testUserId;
- private String testRole;
private String testFirstName;
private String testLastName;
private String testEmail;
@@ -22,7 +20,6 @@ public class UserTest {
@BeforeEach
void setUp() {
testUserId = 1;
- testRole = "Donor";
testFirstName = "John";
testLastName = "Doe";
testEmail = "john.doe@example.com";
@@ -30,12 +27,12 @@ void setUp() {
testPasswordHash = new BCryptPasswordEncoder().encode(testPassword);
}
- private void constructorTest(int userId, String role, String firstName,
+ private void constructorTest(int userId, String firstName,
String lastName, String email, String passwordHash,
String expectedMessage) {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
- () -> new User(userId, role, firstName, lastName, email, passwordHash)
+ () -> new Customer(userId, firstName, lastName, email, passwordHash)
);
assertEquals(expectedMessage, exception.getMessage());
@@ -43,10 +40,9 @@ private void constructorTest(int userId, String role, String firstName,
@Test
void constructorCreatesValidUser() {
- User user = new User(testUserId, testRole, testFirstName,
+ User user = new Customer(testUserId, testFirstName,
testLastName, testEmail, testPasswordHash);
assertEquals(testUserId, user.getUserId());
- assertEquals(testRole, user.getRole());
assertEquals(testFirstName, user.getFirstName());
assertEquals(testLastName, user.getLastName());
assertEquals(testEmail, user.getEmail());
@@ -54,75 +50,70 @@ void constructorCreatesValidUser() {
}
@Test
- void constructorWithNegativeUserIdThrowsException() {
- constructorTest(-1, testRole, testFirstName, testLastName,
- testEmail, testPasswordHash, "User ID must be positive");
- }
-
- @Test
- void constructorWithNullRoleThrowsException() {
- constructorTest(testUserId, null, testFirstName, testLastName,
- testEmail, testPasswordHash, "Role cannot be null or empty");
+ void testInstanceOfCustomer() {
+ User user = new Customer(testUserId, testFirstName,
+ testLastName, testEmail, testPasswordHash);
+ assertTrue(user instanceof Customer);
}
@Test
- void constructorWithEmptyRoleThrowsException() {
- constructorTest(testUserId, "", testFirstName, testLastName,
- testEmail, testPasswordHash, "Role cannot be null or empty");
+ void constructorWithNegativeUserIdThrowsException() {
+ constructorTest(-1, testFirstName, testLastName,
+ testEmail, testPasswordHash, "User ID must be positive");
}
@Test
void constructorWithNullFirstNameThrowsException() {
- constructorTest(testUserId, testRole, null, testLastName,
+ constructorTest(testUserId, null, testLastName,
testEmail, testPasswordHash, "First name cannot be null or empty");
}
@Test
void constructorWithEmptyFirstNameThrowsException() {
- constructorTest(testUserId, testRole, "", testLastName,
+ constructorTest(testUserId, "", testLastName,
testEmail, testPasswordHash, "First name cannot be null or empty");
}
@Test
void constructorWithNullLastNameThrowsException() {
- constructorTest(testUserId, testRole, testFirstName, null,
+ constructorTest(testUserId, testFirstName, null,
testEmail, testPasswordHash, "Last name cannot be null or empty");
}
@Test
void constructorWithEmptyLastNameThrowsException() {
- constructorTest(testUserId, testRole, testFirstName, "",
- testEmail, testPasswordHash, "Last name cannot be null or empty");
+ constructorTest(testUserId, testFirstName,
+ "", testEmail, testPasswordHash, "Last name cannot be null or empty");
}
@Test
void constructorWithNullEmailThrowsException() {
- constructorTest(testUserId, testRole, testFirstName, testLastName,
+ constructorTest(testUserId, testFirstName, testLastName,
null, testPasswordHash, "Email cannot be null or empty");
}
@Test
void constructorWithEmptyEmailThrowsException() {
- constructorTest(testUserId, testRole, testFirstName, testLastName,
+ constructorTest(testUserId, testFirstName, testLastName,
"", testPasswordHash, "Email cannot be null or empty");
}
@Test
void constructorWithNullPasswordHashThrowsException() {
- constructorTest(testUserId, testRole, testFirstName, testLastName,
+ constructorTest(testUserId, testFirstName, testLastName,
testEmail, null, "Password hash cannot be null or empty");
}
@Test
void constructorWithEmptyPasswordHashThrowsException() {
- constructorTest(testUserId, testRole, testFirstName, testLastName,
+ constructorTest(testUserId, testFirstName, testLastName,
testEmail, "", "Password hash cannot be null or empty");
}
@Test
void verifyPasswordReturnsTrueForCorrectPassword() {
- User user = new User(testUserId, testRole, testFirstName,
+ User user = new Customer(testUserId, testFirstName,
testLastName, testEmail, testPasswordHash);
assertTrue(user.verifyPassword(testPassword));
@@ -130,7 +121,7 @@ void verifyPasswordReturnsTrueForCorrectPassword() {
@Test
void verifyPasswordReturnsFalseForIncorrectPassword() {
- User user = new User(testUserId, testRole, testFirstName,
+ User user = new Customer(testUserId, testFirstName,
testLastName, testEmail, testPasswordHash);
assertFalse(user.verifyPassword("wrongPassword"));
@@ -138,7 +129,7 @@ void verifyPasswordReturnsFalseForIncorrectPassword() {
@Test
void verifyPasswordReturnsFalseForNullPassword() {
- User user = new User(testUserId, testRole, testFirstName,
+ User user = new Customer(testUserId, testFirstName,
testLastName, testEmail, testPasswordHash);
assertFalse(user.verifyPassword(null));
@@ -146,9 +137,9 @@ void verifyPasswordReturnsFalseForNullPassword() {
@Test
void verifyPasswordReturnsFalseForEmptyPassword() {
- User user = new User(testUserId, testRole, testFirstName,
+ User user = new Customer(testUserId, testFirstName,
testLastName, testEmail, testPasswordHash);
assertFalse(user.verifyPassword(""));
}
-}
\ No newline at end of file
+}
From 7c90b5bf41097c9724219436ce1636d38a66b122 Mon Sep 17 00:00:00 2001
From: Fredrik Marjoni
Date: Mon, 2 Mar 2026 16:21:01 +0100
Subject: [PATCH 03/17] perf: Add exception handling to increase robust code
---
.../edu/group5/app/model/user/Customer.java | 24 ++++++++++++-------
1 file changed, 15 insertions(+), 9 deletions(-)
diff --git a/src/main/java/edu/group5/app/model/user/Customer.java b/src/main/java/edu/group5/app/model/user/Customer.java
index 5d61aab..3e6465d 100644
--- a/src/main/java/edu/group5/app/model/user/Customer.java
+++ b/src/main/java/edu/group5/app/model/user/Customer.java
@@ -31,19 +31,25 @@ public Customer(int userId,
String email,
String passwordHash) {
- super(userId, "Customer", firstName, lastName, email, passwordHash);
- this.preferences = new ArrayList<>();
+ super(userId, "Customer", firstName, lastName, email, passwordHash);
+ this.preferences = new ArrayList<>();
}
- public List getPreferences() {
- return preferences;
- }
+ public List getPreferences() {
+ return preferences;
+ }
- public void addPreference(int orgNumber) {
- preferences.add(orgNumber);
+ public void addPreference(int orgNumber) {
+ if (!preferences.contains(orgNumber)) {
+ throw new IllegalArgumentException("Organization number already in preferences");
}
+ preferences.add(orgNumber);
+ }
- public void removePreference(int orgNumber) {
- preferences.remove(Integer.valueOf(orgNumber));
+ public void removePreference(int orgNumber) {
+ if (!preferences.contains(orgNumber)) {
+ throw new IllegalArgumentException("Organization number not found in preferences");
}
+ preferences.remove(Integer.valueOf(orgNumber));
+ }
}
\ No newline at end of file
From 02c60a53cb623b03112d9f4c92cc6b6785092a37 Mon Sep 17 00:00:00 2001
From: Fredrik Marjoni
Date: Mon, 2 Mar 2026 16:48:10 +0100
Subject: [PATCH 04/17] Update: update imports to Customer class
---
src/main/java/edu/group5/app/model/user/Customer.java | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/src/main/java/edu/group5/app/model/user/Customer.java b/src/main/java/edu/group5/app/model/user/Customer.java
index 3e6465d..ab7bcd2 100644
--- a/src/main/java/edu/group5/app/model/user/Customer.java
+++ b/src/main/java/edu/group5/app/model/user/Customer.java
@@ -1,7 +1,7 @@
package edu.group5.app.model.user;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.List;
+import java.util.ArrayList;
import edu.group5.app.model.organization.Organization;
@@ -25,11 +25,8 @@ public class Customer extends User {
* @param passwordHash the hashed password of the customer
* @throws IllegalArgumentException if any required field is invalid
*/
- public Customer(int userId,
- String firstName,
- String lastName,
- String email,
- String passwordHash) {
+ public Customer(int userId, String firstName, String lastName,
+ String email, String passwordHash) {
super(userId, "Customer", firstName, lastName, email, passwordHash);
this.preferences = new ArrayList<>();
From 3335ba2780e2a6eaf24fa586512607e60a7bb8a6 Mon Sep 17 00:00:00 2001
From: Fredrik Marjoni
Date: Tue, 3 Mar 2026 10:41:20 +0100
Subject: [PATCH 05/17] fix: fix logic when adding preferance
---
src/main/java/edu/group5/app/model/user/Customer.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/edu/group5/app/model/user/Customer.java b/src/main/java/edu/group5/app/model/user/Customer.java
index ab7bcd2..4f52b19 100644
--- a/src/main/java/edu/group5/app/model/user/Customer.java
+++ b/src/main/java/edu/group5/app/model/user/Customer.java
@@ -37,7 +37,7 @@ public List getPreferences() {
}
public void addPreference(int orgNumber) {
- if (!preferences.contains(orgNumber)) {
+ if (preferences.contains(orgNumber)) {
throw new IllegalArgumentException("Organization number already in preferences");
}
preferences.add(orgNumber);
From c4b2eac9c134a3cd29aa24114a1aab43de00a868 Mon Sep 17 00:00:00 2001
From: Fredrik Marjoni
Date: Tue, 3 Mar 2026 11:36:15 +0100
Subject: [PATCH 06/17] update: update relationship between Customer and
intermideary DonationService
---
.../app/model/donation/DonationService.java | 61 +++++++++++++------
.../edu/group5/app/model/user/Customer.java | 36 ++++++++---
2 files changed, 68 insertions(+), 29 deletions(-)
diff --git a/src/main/java/edu/group5/app/model/donation/DonationService.java b/src/main/java/edu/group5/app/model/donation/DonationService.java
index ecd13da..6f12f91 100644
--- a/src/main/java/edu/group5/app/model/donation/DonationService.java
+++ b/src/main/java/edu/group5/app/model/donation/DonationService.java
@@ -8,34 +8,55 @@
import edu.group5.app.model.user.Customer;
import edu.group5.app.model.donation.DonationRepository;
+/**
+ * DonationService class provides functionality for handling donations in the system.
+ * It interacts with the DonationRepository to manage donation records
+ * and the OrganizationRepository to validate organization information.
+ * The donate method allows a customer to make a donation to a specified organization,
+ * ensuring that the customer, organization, and donation amount are valid before processing the donation.
+ */
public class DonationService {
private final DonationRepository donationRepository;
private final OrganizationRepository organizationRepository;
+ /**
+ * Constructor for DonationService. Initializes the service with the required repositories.
+ * @param donationRepository the repository for managing donation records
+ * @param organizationRepository the repository for managing organization information
+ * @throws IllegalArgumentException if either repository is null
+ */
public DonationService(DonationRepository donationRepository,
OrganizationRepository organizationRepository) {
- this.donationRepository = donationRepository;
- this.organizationRepository = organizationRepository;
+ if (donationRepository == null) {
+ throw new IllegalArgumentException("DonationRepository cannot be null");
+ }
+ if (organizationRepository == null) {
+ throw new IllegalArgumentException("OrganizationRepository cannot be null");
+ }
+ this.donationRepository = donationRepository;
+ this.organizationRepository = organizationRepository;
}
- public boolean donate(Customer customer,
- int orgNumber,
- BigDecimal amount) {
- if (customer == null || amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
- return false;
- }
- Organization org = organizationRepository.findByOrgNumber(orgNumber);
- if (org == null) {
- return false;
- }
- Donation donation = new Donation(
- customer.getUserId(),
- org.getOrgNumber(),
- amount,
- new Timestamp(System.currentTimeMillis())
- );
- donationRepository.addDonation(donation);
- return true;
+ /**
+ * Processes a donation from a customer to a specified organization with a given amount.
+ * Validates the customer, organization number, and donation amount before creating a donation record.
+ * @param customer the customer making the donation
+ * @param orgNumber the organization number to which the donation is made
+ * @param amount the amount of the donation
+ * @return true if the donation is successfully processed, false otherwise
+ */
+ public boolean donate(Customer customer, int orgNumber, BigDecimal amount) {
+ if (customer == null || amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
+ return false;
}
+ Organization org = organizationRepository.findByOrgNumber(orgNumber);
+ if (org == null) {
+ return false;
+ }
+ Donation donation = new Donation(customer.getUserId(), org.getOrgNumber(),
+ amount, new Timestamp(System.currentTimeMillis()));
+ donationRepository.addDonation(donation);
+ return true;
+ }
}
\ No newline at end of file
diff --git a/src/main/java/edu/group5/app/model/user/Customer.java b/src/main/java/edu/group5/app/model/user/Customer.java
index 4f52b19..6ae6109 100644
--- a/src/main/java/edu/group5/app/model/user/Customer.java
+++ b/src/main/java/edu/group5/app/model/user/Customer.java
@@ -36,17 +36,35 @@ public List getPreferences() {
return preferences;
}
- public void addPreference(int orgNumber) {
- if (preferences.contains(orgNumber)) {
- throw new IllegalArgumentException("Organization number already in preferences");
+ public boolean addPreference(int orgNumber) {
+ try {
+ if (orgNumber <= 0) {
+ throw new IllegalArgumentException("Organization number must be a positive integer");
+ }
+ if (preferences.contains(orgNumber)) {
+ throw new IllegalStateException("Organization number already in preferences");
+ }
+ preferences.add(orgNumber);
+ return true;
+ } catch (Exception e) {
+ System.out.println("Add preference failed: " + e.getMessage());
+ return false;
}
- preferences.add(orgNumber);
}
- public void removePreference(int orgNumber) {
- if (!preferences.contains(orgNumber)) {
- throw new IllegalArgumentException("Organization number not found in preferences");
+ public boolean removePreference(int orgNumber) {
+ try {
+ if (orgNumber <= 0) {
+ throw new IllegalArgumentException("Organization number must be a positive integer");
+ }
+ if (!preferences.contains(orgNumber)) {
+ throw new IllegalStateException("Organization number not found in preferences");
+ }
+ preferences.remove(Integer.valueOf(orgNumber));
+ return true;
+ } catch (Exception e) {
+ System.out.println("Remove preference failed: " + e.getMessage());
+ return false;
}
- preferences.remove(Integer.valueOf(orgNumber));
}
-}
\ No newline at end of file
+}
From 6b313cc4ef9b9a4f7c7a264d6dd5e51b93df0d2f Mon Sep 17 00:00:00 2001
From: Fredrik Marjoni
Date: Tue, 3 Mar 2026 13:44:02 +0100
Subject: [PATCH 07/17] update[Customer]: update customer class to better suit
the application
---
.../edu/group5/app/model/user/Customer.java | 91 ++++++++++---------
1 file changed, 49 insertions(+), 42 deletions(-)
diff --git a/src/main/java/edu/group5/app/model/user/Customer.java b/src/main/java/edu/group5/app/model/user/Customer.java
index 6ae6109..2c221b0 100644
--- a/src/main/java/edu/group5/app/model/user/Customer.java
+++ b/src/main/java/edu/group5/app/model/user/Customer.java
@@ -14,57 +14,64 @@
public class Customer extends User {
private List preferences;
- /**
- * Constructs a Customer object.
- * Calls the {@link User} superclass constructor to initialize common user fields
- * and initializes the preferences list.
- * @param userId the unique identifier for the user, must be a positive integer
- * @param firstName the first name of the customer
- * @param lastName the last name of the customer
- * @param email the email address of the customer
- * @param passwordHash the hashed password of the customer
- * @throws IllegalArgumentException if any required field is invalid
- */
- public Customer(int userId, String firstName, String lastName,
- String email, String passwordHash) {
-
+/**
+ * Constructs a new Customer object for new registrations.
+ * The role is automatically set to "Customer" and the preferences list is initialized empty.
+ *
+ * @param userId the unique identifier for the user, must be positive
+ * @param firstName the first name of the customer
+ * @param lastName the last name of the customer
+ * @param email the email address of the customer
+ * @param passwordHash the hashed password of the customer
+ * @throws IllegalArgumentException if any parameter is invalid (null, empty, or userId ≤ 0)
+ */
+public Customer(int userId, String firstName, String lastName,
+ String email, String passwordHash) {
super(userId, "Customer", firstName, lastName, email, passwordHash);
this.preferences = new ArrayList<>();
- }
+}
+
+/**
+ * Constructs a Customer object for existing users loaded from the database
+ * or for cases where the role is already defined.
+ * The role should normally be "Customer", but this constructor allows flexibility
+ * (e.g., for testing or future user types). Preferences list is initialized empty.
+ *
+ * @param userId the unique identifier for the user, must be positive
+ * @param role the role of the user (should be "Customer" for customer instances)
+ * @param firstName the first name of the customer
+ * @param lastName the last name of the customer
+ * @param email the email address of the customer
+ * @param passwordHash the hashed password of the customer
+ * @throws IllegalArgumentException if any parameter is invalid (null, empty, or userId ≤ 0)
+ */
+public Customer(int userId, String role, String firstName, String lastName,
+ String email, String passwordHash) {
+ super(userId, role, firstName, lastName, email, passwordHash);
+ this.preferences = new ArrayList<>();
+}
public List getPreferences() {
return preferences;
}
- public boolean addPreference(int orgNumber) {
- try {
- if (orgNumber <= 0) {
- throw new IllegalArgumentException("Organization number must be a positive integer");
- }
- if (preferences.contains(orgNumber)) {
- throw new IllegalStateException("Organization number already in preferences");
- }
- preferences.add(orgNumber);
- return true;
- } catch (Exception e) {
- System.out.println("Add preference failed: " + e.getMessage());
- return false;
+ public void addPreference(int orgNumber) {
+ if (orgNumber <= 0) {
+ throw new IllegalArgumentException("Organization number must be a positive integer");
}
+ if (preferences.contains(orgNumber)) {
+ throw new IllegalArgumentException("Organization number already in preferences");
+ }
+ preferences.add(orgNumber);
}
- public boolean removePreference(int orgNumber) {
- try {
- if (orgNumber <= 0) {
- throw new IllegalArgumentException("Organization number must be a positive integer");
- }
- if (!preferences.contains(orgNumber)) {
- throw new IllegalStateException("Organization number not found in preferences");
- }
- preferences.remove(Integer.valueOf(orgNumber));
- return true;
- } catch (Exception e) {
- System.out.println("Remove preference failed: " + e.getMessage());
- return false;
+ public void removePreference(int orgNumber) {
+ if (orgNumber <= 0) {
+ throw new IllegalArgumentException("Organization number must be a positive integer");
+ }
+ if (!preferences.contains(orgNumber)) {
+ throw new IllegalArgumentException("Organization number not found in preferences");
}
+ preferences.remove(Integer.valueOf(orgNumber));
}
-}
+}
\ No newline at end of file
From a8f6f9fb7c2ff4ab417b41771d931c51aefc0326 Mon Sep 17 00:00:00 2001
From: Fredrik Marjoni
Date: Tue, 3 Mar 2026 13:48:05 +0100
Subject: [PATCH 08/17] fix[DonationService]: Change timestamp syntax to better
readability and usability
---
.../java/edu/group5/app/model/donation/DonationService.java | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/main/java/edu/group5/app/model/donation/DonationService.java b/src/main/java/edu/group5/app/model/donation/DonationService.java
index 6f12f91..270b00e 100644
--- a/src/main/java/edu/group5/app/model/donation/DonationService.java
+++ b/src/main/java/edu/group5/app/model/donation/DonationService.java
@@ -1,4 +1,5 @@
package edu.group5.app.service;
+import java.time.Instant;
import java.math.BigDecimal;
import java.sql.Timestamp;
@@ -55,7 +56,7 @@ public boolean donate(Customer customer, int orgNumber, BigDecimal amount) {
return false;
}
Donation donation = new Donation(customer.getUserId(), org.getOrgNumber(),
- amount, new Timestamp(System.currentTimeMillis()));
+ amount, Timestamp.from(Instant.now()));
donationRepository.addDonation(donation);
return true;
}
From dcc32bf2a0de62c848f1a06f34db3a6c104a7fcd Mon Sep 17 00:00:00 2001
From: Fredrik Marjoni
Date: Tue, 3 Mar 2026 17:15:35 +0100
Subject: [PATCH 09/17] perf: increase performance of Organizationrepository
---
.../app/model/donation/DonationService.java | 2 +-
.../organization/OrganizationRepository.java | 65 ++++++++++---------
.../model/organization/OrganizationTest.java | 2 +-
3 files changed, 37 insertions(+), 32 deletions(-)
diff --git a/src/main/java/edu/group5/app/model/donation/DonationService.java b/src/main/java/edu/group5/app/model/donation/DonationService.java
index 270b00e..0b6aafd 100644
--- a/src/main/java/edu/group5/app/model/donation/DonationService.java
+++ b/src/main/java/edu/group5/app/model/donation/DonationService.java
@@ -55,7 +55,7 @@ public boolean donate(Customer customer, int orgNumber, BigDecimal amount) {
if (org == null) {
return false;
}
- Donation donation = new Donation(customer.getUserId(), org.getOrgNumber(),
+ Donation donation = new Donation(customer.getUserId(), org.orgNumber(),
amount, Timestamp.from(Instant.now()));
donationRepository.addDonation(donation);
return true;
diff --git a/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java b/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java
index 98e2f0a..e48be9e 100644
--- a/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java
+++ b/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java
@@ -1,58 +1,63 @@
package edu.group5.app.model.organization;
import edu.group5.app.model.Repository;
+import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.ObjectMapper;
import java.util.HashMap;
import java.util.Map;
-import java.util.Objects;
/**
* Handles the business logic associated with organizations
*/
public class OrganizationRepository extends Repository {
+ private final HashMap grandMap;
- @SuppressWarnings("unchecked")
public OrganizationRepository(Object[] input) {
+ grandMap = new HashMap<>();
+ ObjectMapper mapper = new ObjectMapper();
- if (input == null) {
- throw new NullPointerException("Input cannot be null");
- }
for (Object obj : input) {
- Map map = (Map) obj;
+ HashMap contentMap =
+ mapper.convertValue(obj, new TypeReference>() {});
+
+ String orgNumberStr = ((String) contentMap.get("org_number")).replaceAll("\\s", "");
+ int orgNumber = Integer.parseInt(orgNumberStr);
- int orgNumber = Integer.parseInt((String) map.get("org_number"));
- String name = (String) map.get("name");
+ String name = (String) contentMap.get("name");
- boolean trusted =
- "approved".equalsIgnoreCase((String) map.get("status"));
- String url = (String) map.get("url");
-
- boolean isPreApproved = (Boolean) map.get("is_pre_approved");
+ boolean trusted = "approved".equalsIgnoreCase((String) contentMap.get("status"));
- Organization organization = new Organization(
- orgNumber, name, trusted, url,
- isPreApproved,
- "" // description not included in API
- );
+ String websiteURL = (String) contentMap.get("url");
+
+ boolean isPreApproved = true;
+ if (!contentMap.containsKey("is_pre_approved")) {
+ isPreApproved = false;
+ } else if ((boolean)contentMap.get("is_pre_approved") == false) {
+ isPreApproved = false;
+ }
- content.put(orgNumber, organization);
+ String description = "Information about " + name;
+
+ Organization org = new Organization(orgNumber, name, trusted, websiteURL, isPreApproved, description);
+
+ grandMap.put(org.orgNumber(), org);
}
-}
+ }
- /**
- * Gets all trusted organizations in the repository
- * @return all organizations with trusted = true
- */
- public Map getTrustedOrganizations() {
+/**
+ * Gets all trusted organizations in the repository
+ * @return all organizations with trusted = true
+ */
+public Map getTrustedOrganizations() {
Map trustedOrganizations = new HashMap<>();
- content.forEach((orgNr, org) -> {
- if (org.trusted()) {
- trustedOrganizations.put(orgNr, org);
- }
+ grandMap.forEach((orgNr, org) -> {
+ if (org.trusted()) {
+ trustedOrganizations.put(orgNr, org);
+ }
});
return trustedOrganizations;
- }
+}
}
diff --git a/src/test/java/edu/group5/app/model/organization/OrganizationTest.java b/src/test/java/edu/group5/app/model/organization/OrganizationTest.java
index 7f1b898..3e3f338 100644
--- a/src/test/java/edu/group5/app/model/organization/OrganizationTest.java
+++ b/src/test/java/edu/group5/app/model/organization/OrganizationTest.java
@@ -20,7 +20,7 @@ void constructor_CreatesAnOrganizationWhenInputIsValid() {
assertAll(
() -> assertEquals(1, org.orgNumber()),
() -> assertEquals("Org", org.name()),
- () -> assertTrue(org.trusted()),
+ () -> assertTrue(org.status()),
() -> assertEquals("org.com", org.websiteURL()),
() -> assertTrue(org.isPreApproved()),
() -> assertEquals("Org description", org.description())
From adce1166037961ef1d22d892800e0055de324fb6 Mon Sep 17 00:00:00 2001
From: Fredrik Marjoni
Date: Wed, 4 Mar 2026 12:39:57 +0100
Subject: [PATCH 10/17] fix: fix Organizational classes by fixing constructor
and JUnit tests
---
.../organization/OrganizationRepository.java | 3 ++
.../OrganizationRepositoryTest.java | 54 ++++++++++++++-----
.../model/organization/OrganizationTest.java | 2 +-
3 files changed, 44 insertions(+), 15 deletions(-)
diff --git a/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java b/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java
index e48be9e..4ba589e 100644
--- a/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java
+++ b/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java
@@ -14,6 +14,9 @@ public class OrganizationRepository extends Repository {
private final HashMap grandMap;
public OrganizationRepository(Object[] input) {
+ if (input == null) {
+ throw new IllegalArgumentException("The input cannot be null");
+ }
grandMap = new HashMap<>();
ObjectMapper mapper = new ObjectMapper();
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 3a3392e..184b5e7 100644
--- a/src/test/java/edu/group5/app/model/organization/OrganizationRepositoryTest.java
+++ b/src/test/java/edu/group5/app/model/organization/OrganizationRepositoryTest.java
@@ -3,7 +3,8 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import java.util.HashMap;
+import edu.group5.app.model.user.Customer;
+
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
@@ -14,24 +15,49 @@ class OrganizationRepositoryTest {
@BeforeEach
void setUp() {
- Map content = new HashMap<>();
-
- Organization trustedOrganization1 = new Organization(1, "Trusted Org1", true, "org.com", true, "description");
- Organization trustedOrganization2 = new Organization(2, "Trusted Org2", true, "org.com", true, "description");
- Organization untrustedOrganization1 = new Organization(3, "Untrusted Org1", false, "org.com", true, "description");
- Organization untrustedOrganization2 = new Organization(4, "Untrusted Org2", false, "org.com", true, "description");
-
- content.put(1, trustedOrganization1);
- content.put(2, trustedOrganization2);
- content.put(3, untrustedOrganization1);
- content.put(4, untrustedOrganization2);
-
+ Object[] content = new Object[] {
+ Map.of(
+ "org_number", "1",
+ "name", "Trusted Org1",
+ "status", "approved",
+ "url", "org.com",
+ "is_pre_approved", true
+ ),
+ Map.of(
+ "org_number", "2",
+ "name", "Trusted Org2",
+ "status", "approved",
+ "url", "org.com",
+ "is_pre_approved", true
+ ),
+ Map.of(
+ "org_number", "3",
+ "name", "Untrusted Org1",
+ "status", "pending",
+ "url", "org.com",
+ "is_pre_approved", true
+ ),
+ Map.of(
+ "org_number", "4",
+ "name", "Untrusted Org2",
+ "status", "pending",
+ "url", "org.com",
+ "is_pre_approved", true
+ )
+ };
repository = new OrganizationRepository(content);
}
+ private void constructorTest(Object[] input, String expectedMessage) {
+ IllegalArgumentException exception = assertThrows(
+ IllegalArgumentException.class,
+ () -> new OrganizationRepository(input)
+ );
+ assertEquals(expectedMessage, exception.getMessage());
+ }
@Test
void constructor_ThrowsWhenContentIsNull() {
- assertThrows(NullPointerException.class, () -> new OrganizationRepository(null));
+ constructorTest(null, "The input cannot be null");
}
@Test
diff --git a/src/test/java/edu/group5/app/model/organization/OrganizationTest.java b/src/test/java/edu/group5/app/model/organization/OrganizationTest.java
index 3e3f338..7f1b898 100644
--- a/src/test/java/edu/group5/app/model/organization/OrganizationTest.java
+++ b/src/test/java/edu/group5/app/model/organization/OrganizationTest.java
@@ -20,7 +20,7 @@ void constructor_CreatesAnOrganizationWhenInputIsValid() {
assertAll(
() -> assertEquals(1, org.orgNumber()),
() -> assertEquals("Org", org.name()),
- () -> assertTrue(org.status()),
+ () -> assertTrue(org.trusted()),
() -> assertEquals("org.com", org.websiteURL()),
() -> assertTrue(org.isPreApproved()),
() -> assertEquals("Org description", org.description())
From f81c347ae397acf0e457cd1ea27fb834be5254b6 Mon Sep 17 00:00:00 2001
From: Fredrik Marjoni
Date: Thu, 5 Mar 2026 08:53:15 +0100
Subject: [PATCH 11/17] fix: fix login method's unnecessary String casting of
char[] argument
---
src/main/java/edu/group5/app/model/user/UserService.java | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
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 5497e1a..a979eee 100644
--- a/src/main/java/edu/group5/app/model/user/UserService.java
+++ b/src/main/java/edu/group5/app/model/user/UserService.java
@@ -28,7 +28,7 @@ public boolean registerUser(String role, String firstName, String lastName,
return true;
}
- public boolean login(String email, char[] password) {
+ public boolean login(String email, String password) {
if (email == null || email.trim().isEmpty() || password == null) {
return false;
}
@@ -36,8 +36,7 @@ public boolean login(String email, char[] password) {
if (user == null) {
return false;
}
- String passwordString = new String(password);
- if (user.verifyPassword(passwordString)) {
+ if (user.verifyPassword(password)) {
return true;
}
return false;
From be3995b616c7d1d3fcb98e11a9320a99c86f2ea7 Mon Sep 17 00:00:00 2001
From: Fredrik Marjoni
Date: Thu, 5 Mar 2026 09:25:52 +0100
Subject: [PATCH 12/17] style[OrganizationRepository]: change style on method
for better readability
---
.../model/organization/OrganizationRepository.java | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java b/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java
index 4ba589e..3147116 100644
--- a/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java
+++ b/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java
@@ -52,15 +52,15 @@ public OrganizationRepository(Object[] input) {
* Gets all trusted organizations in the repository
* @return all organizations with trusted = true
*/
-public Map getTrustedOrganizations() {
+ public Map getTrustedOrganizations() {
Map trustedOrganizations = new HashMap<>();
grandMap.forEach((orgNr, org) -> {
- if (org.trusted()) {
- trustedOrganizations.put(orgNr, org);
- }
+ if (org.trusted()) {
+ trustedOrganizations.put(orgNr, org);
+ }
});
-
return trustedOrganizations;
-}
+ }
+
}
From 6c34cfcfe52906247daeba0027d48c74ceb83700 Mon Sep 17 00:00:00 2001
From: Fredrik Marjoni
Date: Thu, 5 Mar 2026 12:47:13 +0100
Subject: [PATCH 13/17] Fix: Fix OrganizationRepository constructor and update
related classes
---
.../edu/group5/app/model/DBRepository.java | 4 --
.../java/edu/group5/app/model/Repository.java | 2 +-
.../app/model/donation/DonationService.java | 12 ++---
.../organization/OrganizationRepository.java | 17 ++++---
.../edu/group5/app/model/user/Customer.java | 2 -
.../group5/app/model/user/UserRepository.java | 48 +++++++++++++++++++
.../group5/app/model/user/UserService.java | 16 +++----
.../OrganizationRepositoryTest.java | 2 -
8 files changed, 70 insertions(+), 33 deletions(-)
create mode 100644 src/main/java/edu/group5/app/model/user/UserRepository.java
diff --git a/src/main/java/edu/group5/app/model/DBRepository.java b/src/main/java/edu/group5/app/model/DBRepository.java
index 1196eae..9dac3c0 100644
--- a/src/main/java/edu/group5/app/model/DBRepository.java
+++ b/src/main/java/edu/group5/app/model/DBRepository.java
@@ -1,9 +1,5 @@
package edu.group5.app.model;
-
-
-import java.util.HashMap;
import java.util.Map;
-
/**
* Abstract base class for repositories that store their data
* in a database-related structure.
diff --git a/src/main/java/edu/group5/app/model/Repository.java b/src/main/java/edu/group5/app/model/Repository.java
index 5396b2f..032f307 100644
--- a/src/main/java/edu/group5/app/model/Repository.java
+++ b/src/main/java/edu/group5/app/model/Repository.java
@@ -12,7 +12,7 @@ public abstract class Repository {
*
* @param content the underlying data structure used to store entities
*/
- public Repository(Map content) {
+ protected Repository(Map content) {
this.content = content;
}
diff --git a/src/main/java/edu/group5/app/model/donation/DonationService.java b/src/main/java/edu/group5/app/model/donation/DonationService.java
index 0b6aafd..11dde6c 100644
--- a/src/main/java/edu/group5/app/model/donation/DonationService.java
+++ b/src/main/java/edu/group5/app/model/donation/DonationService.java
@@ -1,13 +1,11 @@
-package edu.group5.app.service;
+package edu.group5.app.model.donation;
import java.time.Instant;
import java.math.BigDecimal;
import java.sql.Timestamp;
-import edu.group5.app.model.donation.Donation;
import edu.group5.app.model.organization.Organization;
import edu.group5.app.model.organization.OrganizationRepository;
import edu.group5.app.model.user.Customer;
-import edu.group5.app.model.donation.DonationRepository;
/**
* DonationService class provides functionality for handling donations in the system.
@@ -47,16 +45,16 @@ public DonationService(DonationRepository donationRepository,
* @param amount the amount of the donation
* @return true if the donation is successfully processed, false otherwise
*/
- public boolean donate(Customer customer, int orgNumber, BigDecimal amount) {
- if (customer == null || amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
+ public boolean donate(Customer customer, int orgNumber, BigDecimal amount, String paymentMethod) {
+ if (customer == null || amount == null || amount.compareTo(BigDecimal.ZERO) <= 0 || paymentMethod.isBlank()) {
return false;
}
Organization org = organizationRepository.findByOrgNumber(orgNumber);
if (org == null) {
return false;
}
- Donation donation = new Donation(customer.getUserId(), org.orgNumber(),
- amount, Timestamp.from(Instant.now()));
+ Donation donation = new Donation(1, customer.getUserId(), org.orgNumber(), /* TODO fix incrementation of donation */
+ amount, Timestamp.from(Instant.now()), paymentMethod);
donationRepository.addDonation(donation);
return true;
}
diff --git a/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java b/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java
index 3147116..53b4241 100644
--- a/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java
+++ b/src/main/java/edu/group5/app/model/organization/OrganizationRepository.java
@@ -14,10 +14,11 @@ public class OrganizationRepository extends Repository {
private final HashMap grandMap;
public OrganizationRepository(Object[] input) {
+ super(new HashMap<>());
+ grandMap = new HashMap<>();
if (input == null) {
throw new IllegalArgumentException("The input cannot be null");
}
- grandMap = new HashMap<>();
ObjectMapper mapper = new ObjectMapper();
for (Object obj : input) {
@@ -33,12 +34,7 @@ public OrganizationRepository(Object[] input) {
String websiteURL = (String) contentMap.get("url");
- boolean isPreApproved = true;
- if (!contentMap.containsKey("is_pre_approved")) {
- isPreApproved = false;
- } else if ((boolean)contentMap.get("is_pre_approved") == false) {
- isPreApproved = false;
- }
+ boolean isPreApproved = Boolean.TRUE.equals(contentMap.get("is_pre_approved"));
String description = "Information about " + name;
@@ -63,4 +59,11 @@ public Map getTrustedOrganizations() {
return trustedOrganizations;
}
+ public Organization findByOrgNumber(int orgNumber) {
+ if (orgNumber == 0 || orgNumber <= 0) {
+ throw new IllegalArgumentException("The Organization number must be a positive integer");
+ }
+ return grandMap.get(orgNumber);
+ }
+
}
diff --git a/src/main/java/edu/group5/app/model/user/Customer.java b/src/main/java/edu/group5/app/model/user/Customer.java
index 2c221b0..5d3fa7e 100644
--- a/src/main/java/edu/group5/app/model/user/Customer.java
+++ b/src/main/java/edu/group5/app/model/user/Customer.java
@@ -3,8 +3,6 @@
import java.util.List;
import java.util.ArrayList;
-import edu.group5.app.model.organization.Organization;
-
/**
* Customer class represents a customer in the system.
* It extends the User class and includes additional functionality specific to customers,
diff --git a/src/main/java/edu/group5/app/model/user/UserRepository.java b/src/main/java/edu/group5/app/model/user/UserRepository.java
new file mode 100644
index 0000000..18bbd09
--- /dev/null
+++ b/src/main/java/edu/group5/app/model/user/UserRepository.java
@@ -0,0 +1,48 @@
+package edu.group5.app.model.user;
+
+import java.util.HashMap;
+
+import edu.group5.app.model.DBRepository;
+
+public class UserRepository extends DBRepository{
+
+ /**
+ * Constructs DonationRepository using Hashmap,
+ * and extends the content from DBRepository.
+ * @param content the underlying map used to store donations,
+ * where the key represents the donation ID
+ */
+ public UserRepository(HashMap content) {
+ super(content);
+ if (content == null) {
+ throw new IllegalArgumentException("Content cannot be null");
+ }
+ }
+
+ /**
+ * Adds a new donation to the repository
+ *
+ * The donation is stored using its {@code donationId} as the key.
+ * If a donation with the same ID already exists, the donation
+ * will not be added.
+ *
+ *
+ * @param donation the donation to add
+ * @return {@code true} if the donation was successfully added, and
+ * {@code false} if a donation with the same ID already exists
+ */
+ public boolean addUser(User user) {
+ if (user == null) {
+ throw new IllegalArgumentException("Donation cannot be null");
+ }
+ if (content.containsKey(user.getUserId())){
+ return false;
+ }
+ content.put(user.getUserId(), user);
+ return true;
+ }
+
+ public User findUserByEmail(String email) {
+ return null;
+ }
+}
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 a979eee..d75d380 100644
--- a/src/main/java/edu/group5/app/model/user/UserService.java
+++ b/src/main/java/edu/group5/app/model/user/UserService.java
@@ -23,7 +23,9 @@ public boolean registerUser(String role, String firstName, String lastName,
User user;
if (role.equalsIgnoreCase("Customer")) {
user = new Customer(0, firstName, lastName, email, passwordHash);
- }
+ } else {
+ return false;
+ }
userRepository.addUser(user);
return true;
}
@@ -32,13 +34,7 @@ public boolean login(String email, String password) {
if (email == null || email.trim().isEmpty() || password == null) {
return false;
}
- User user = userRepository.findByEmail(email);
- if (user == null) {
- return false;
- }
- if (user.verifyPassword(password)) {
- return true;
- }
- return false;
-}
+ User user = userRepository.findUserByEmail(email);
+ return user != null && user.verifyPassword(password);
+ }
}
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 184b5e7..1913988 100644
--- a/src/test/java/edu/group5/app/model/organization/OrganizationRepositoryTest.java
+++ b/src/test/java/edu/group5/app/model/organization/OrganizationRepositoryTest.java
@@ -3,8 +3,6 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import edu.group5.app.model.user.Customer;
-
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
From 4ac5cdd56622da99b2eca3567443193829478cf8 Mon Sep 17 00:00:00 2001
From: Fredrik Marjoni
Date: Sat, 7 Mar 2026 11:52:24 +0100
Subject: [PATCH 14/17] perf: unify repository constructors, unify Backend
classes, standardize exports, update services, and add comprehensive tests
---
pom.xml | 35 ++-
.../edu/group5/app/model/DBRepository.java | 24 ++
.../model/donation/DonationRepository.java | 74 ++++-
.../app/model/donation/DonationService.java | 8 +-
.../organization/OrganizationRepository.java | 61 ++++-
.../edu/group5/app/model/user/Customer.java | 28 +-
.../java/edu/group5/app/model/user/User.java | 17 +-
.../group5/app/model/user/UserRepository.java | 110 ++++++--
.../group5/app/model/user/UserService.java | 37 ++-
.../donation/DonationRepositoryTest.java | 256 +++++++++++-------
.../model/donation/DonationServiceTest.java | 116 ++++++++
.../OrganizationRepositoryTest.java | 65 +++++
.../group5/app/model/user/CustomerTest.java | 64 +++++
.../app/model/user/UserRepositoryTest.java | 124 +++++++++
.../app/model/user/UserServiceTest.java | 108 ++++++++
15 files changed, 949 insertions(+), 178 deletions(-)
create mode 100644 src/test/java/edu/group5/app/model/donation/DonationServiceTest.java
create mode 100644 src/test/java/edu/group5/app/model/user/UserRepositoryTest.java
create mode 100644 src/test/java/edu/group5/app/model/user/UserServiceTest.java
diff --git a/pom.xml b/pom.xml
index 6320c10..7a55551 100644
--- a/pom.xml
+++ b/pom.xml
@@ -31,6 +31,13 @@
org.openjfxjavafx-controls${javafx.version}
+ win
+
+
+ org.openjfx
+ javafx-graphics
+ ${javafx.version}
+ win
@@ -38,11 +45,6 @@
spring-security-crypto7.0.2
-
- commons-logging
- commons-logging
- 1.3.5
- tools.jackson.core
@@ -50,6 +52,16 @@
3.1.0compile
+
+ org.springframework
+ spring-core
+ 6.1.10
+
+
+ org.slf4j
+ slf4j-simple
+ 2.0.9
+
@@ -111,6 +123,19 @@
shade
+
+
+ *:*
+
+ META-INF/*.SF
+ META-INF/*.DSA
+ META-INF/*.RSA
+ META-INF/NOTICE
+ META-INF/LICENSE
+ META-INF.versions.9.module-info
+
+
+ edu.group5.app.App
diff --git a/src/main/java/edu/group5/app/model/DBRepository.java b/src/main/java/edu/group5/app/model/DBRepository.java
index 9dac3c0..799a977 100644
--- a/src/main/java/edu/group5/app/model/DBRepository.java
+++ b/src/main/java/edu/group5/app/model/DBRepository.java
@@ -1,5 +1,7 @@
package edu.group5.app.model;
+import java.util.HashMap;
import java.util.Map;
+import java.util.List;
/**
* Abstract base class for repositories that store their data
* in a database-related structure.
@@ -10,6 +12,7 @@
*
*/
public abstract class DBRepository extends Repository {
+ protected final Map contentLock;
/**
* Constructs a DBRepository with the given content.
*
@@ -17,5 +20,26 @@ public abstract class DBRepository extends Repository {
*/
protected DBRepository(Map content) {
super(content);
+ this.contentLock = new HashMap<>();
}
+
+ protected void updateContentLock() {
+ synchronized (contentLock) {
+ contentLock.clear();
+ contentLock.putAll(this.content);
+ }
+ }
+
+ public void addContent(K key, V value) {
+ synchronized (contentLock) {
+ content.put(key, value);
+ }
+ }
+
+ /**
+ * Exports the repository content as a list of Object arrays, where each array represents a row of data.
+ * This method is intended for converting the repository content into a format suitable for database storage or export.
+ * @return a List of Object arrays representing the repository content for database export
+ */
+ public abstract List