diff --git a/README.md b/README.md
index f4eddd7..15bdbf7 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,15 +207,14 @@ https://git.ntnu.no/Group-5/Help-Me-Help
```bash
mvn javafx:run
```
- ```
-
-**Run From JAR: (Windows)
+**Run From JAR: (Windows)**
1. Download Project JAR:
Go to repository and download the JAR from the jar release.
2. Run the JAR in Terminal:
+
```bash
java --module-path "path\to\javafx\sdk" --add-modules javafx.controls -jar path\to\jar.jar
diff --git a/src/main/java/edu/group5/app/control/AuthController.java b/src/main/java/edu/group5/app/control/AuthController.java
index 7c34afd..d3d4ebe 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,43 +78,112 @@ 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';
+ // Checks if any input is too long.
+ 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;
}
+ // Privacy policy pop-up.
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" +
- "By creating an account, you accept the right of our app to store this information.");
+ "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}.
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,
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);
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/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/main/java/edu/group5/app/model/user/UserRepository.java b/src/main/java/edu/group5/app/model/user/UserRepository.java
index 07f9ca8..aec3e9d 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,13 @@
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";
@@ -14,7 +20,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