From f64ebc41707fdddf35268a37b4e52b86ec1440a4 Mon Sep 17 00:00:00 2001
From: Roar
Date: Tue, 24 Feb 2026 14:09:14 +0100
Subject: [PATCH 01/77] Delete Classes.
Deleted non-relevant classes for web-scraping objects/methods.
---
.../java/ntnu/sytemutvikling/team6/Main.java | 7 --
.../sytemutvikling/team6/models/Charity.java | 98 -------------------
.../team6/models/CharityRegistry.java | 0
.../sytemutvikling/team6/models/Donation.java | 79 ---------------
.../team6/models/DonationRegistry.java | 5 -
.../sytemutvikling/team6/models/Feedback.java | 65 ------------
.../sytemutvikling/team6/models/Message.java | 0
.../sytemutvikling/team6/models/Settings.java | 5 -
.../team6/models/UserRegistry.java | 6 --
.../team6/service/AuthenticationService.java | 4 -
.../team6/service/CharityService.java | 4 -
.../team6/service/DonationService.java | 4 -
.../team6/service/FeedbackService.java | 4 -
13 files changed, 281 deletions(-)
delete mode 100644 helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/Main.java
delete mode 100644 helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Charity.java
delete mode 100644 helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/CharityRegistry.java
delete mode 100644 helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Donation.java
delete mode 100644 helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/DonationRegistry.java
delete mode 100644 helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Feedback.java
delete mode 100644 helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Message.java
delete mode 100644 helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Settings.java
delete mode 100644 helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/UserRegistry.java
delete mode 100644 helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/service/AuthenticationService.java
delete mode 100644 helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/service/CharityService.java
delete mode 100644 helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/service/DonationService.java
delete mode 100644 helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/service/FeedbackService.java
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/Main.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/Main.java
deleted file mode 100644
index d265ebe..0000000
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/Main.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package ntnu.sytemutvikling.team6;
-
-public class Main {
- public static void main(String[] args) {
- System.out.println("Hello world!");
- }
-}
\ No newline at end of file
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Charity.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Charity.java
deleted file mode 100644
index 4a389f4..0000000
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Charity.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/**
- * This class represents a charity organization. It contains information about the charity such as its name, description, total donations, verification status, and category.
- *
- * @author Adrian Balunan
- */
-package ntnu.sytemutvikling.team6.models;
-
-import java.util.List;
-import java.util.UUID;
-import java.util.ArrayList;
-
-abstract class Charity {
- /* UUID for uniquely identifying each charity */
- private UUID id;
-
- /* Name of the charity */
- private String name;
-
- /* Description of the charity's mission and activities */
- private String description;
-
- /* Total Donations received */
- private int totalDonations;
-
- /* Is the charity verified? */
- private boolean isVerified;
-
- /* Category for the charity */
- private String category;
-
- /* List that contains the charity's Feedbacks */
- private List feedbacks;
-
- /**
- * Konstructor for creating a new charity.
- * The ID is generated automatically using UUID.
- * Total donations are initialized to 0.
- * The charity is unverified by default.
- *
- * @param name
- * @param description
- * @param category
- */
- public Charity(String name, String description, String category) {
- this.id = UUID.randomUUID();
- this.name = name;
- this.description = description;
- this.totalDonations = 0;
- this.isVerified = false;
- this.feedbacks = new ArrayList<>();
- this.category = category;
- }
-
- /**
- * Getters for the charity's attributes.
- */
- public UUID getId() {
- return id;
- }
- public String getCategory() {
- return category;
- }
- public String getName() {
- return name;
- }
- public String getDescription() {
- return description;
- }
- public int getTotalDonations() {
- return totalDonations;
- }
- public boolean isVerified() {
- return isVerified;
- }
-
- /**
- * Setter for verification status.
- * This one sets the charity as verified.
- */
- public void setVerified() {
- this.isVerified = true;
- }
-
- /**
- * Setter for verification status.
- * This one sets the charity as unverified.
- */
- public void setUnverified() {
- this.isVerified = false;
- }
-
- /**
- * Setter for total donations. This method is used to update the total donations when a new donation is made.
- */
- public void setTotalDonations(int amount) {
- this.totalDonations += amount;
- }
-}
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/CharityRegistry.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/CharityRegistry.java
deleted file mode 100644
index e69de29..0000000
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Donation.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Donation.java
deleted file mode 100644
index 78569b2..0000000
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Donation.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package ntnu.sytemutvikling.team6.models;
-
-import java.time.LocalDateTime;
-import java.util.UUID;
-
-public class Donation {
- /* UUID for uniquely identifying each donation */
- private UUID charityId;
-
- /* Ammount of money donated */
- private double amount;
-
- /* Date and time of the donation */
- private LocalDateTime date;
-
- /* The charity that received the donation */
- private Charity charity;
-
- /* The user/donor that made the donation */
- private User donor;
-
- /**
- * Is the donation made anonymously?
- * This can be null if the donation was made anonymously.
- *
- */
- private boolean isAnonymous;
-
- /**
- * Constructor for creating a new donation.
- * The charityId is generated automatically using UUID.
- * If the donation is made anonymously, the isAnonymous parameter is set to true.
- * @param amount
- * @param date
- * @param charity
- * @param donor
- */
- public Donation(double amount, LocalDateTime date, Charity charity, User donor) {
- this.charityId = UUID.randomUUID();
- this.amount = amount;
- this.date = date;
- this.charity = charity;
- this.donor = donor;
-
-
- // ASSUMES that this is the way to get the anonymous setting from the user's settings.
- if (donor.getSettings().getAnonymous() == false) {
- this.isAnonymous = true;
- } else {
- this.isAnonymous = false;
-
- }
- }
-
- /* Getters for the donation's attributes */
- public boolean isAnonymous() {
- return isAnonymous;
- }
-
- public UUID getCharityId() {
- return charityId;
- }
-
- public double getAmount() {
- return amount;
- }
-
- public LocalDateTime getDate() {
- return date;
- }
-
- public Charity getCharity() {
- return charity;
- }
-
- public User getDonor() {
- return donor;
- }
-}
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/DonationRegistry.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/DonationRegistry.java
deleted file mode 100644
index f565298..0000000
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/DonationRegistry.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package ntnu.sytemutvikling.team6.models;
-
-public class DonationRegistry {
-
-}
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Feedback.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Feedback.java
deleted file mode 100644
index 891dce1..0000000
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Feedback.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package ntnu.sytemutvikling.team6.models;
-
-import java.time.LocalDateTime;
-import java.util.UUID;
-
-public class Feedback {
- /* Feedback id */
- private UUID feedbackId;
-
- /**
- * The author of the feedback
- * If annonymous the presentation of the user will be "Anonymous".
- */
- private User user;
-
- /* The details of the feedback*/
- private String comment;
-
- /* The date and time when the feedback was given */
- private LocalDateTime date;
-
- /* Is the feedback given anonymously? */
- private boolean isAnonymous;
-
- /**
- * Constructor for creating a new feedback.
- *
- * @param user The user who gives the feedback.
- * @param comment The content of the feedback.
- */
- public Feedback(User user, String comment) {
- this.feedbackId = UUID.randomUUID();
- this.user = user;
- this.comment = comment;
- this.date = LocalDateTime.now();
-
- // ASSUMES that this is the way to get the anonymous setting from the user's settings.
- if (user.getSettings().getAnonymous() == false) {
- this.isAnonymous = true;
- } else {
- this.isAnonymous = false;
- }
- }
-
- /**
- * Getters for the feedback's attributes.
- *
- * @return The feedback's attributes.
- */
- public UUID getFeedbackId() {
- return feedbackId;
- }
- public String getComment() {
- return comment;
- }
- public LocalDateTime getDate() {
- return date;
- }
- public User getUser() {
- return user;
- }
- public boolean isAnonymous() {
- return isAnonymous;
- }
-}
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Message.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Message.java
deleted file mode 100644
index e69de29..0000000
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Settings.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Settings.java
deleted file mode 100644
index 71e218a..0000000
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Settings.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package ntnu.sytemutvikling.team6.models;
-
-public class Settings {
-
-}
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/UserRegistry.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/UserRegistry.java
deleted file mode 100644
index 7b97e02..0000000
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/UserRegistry.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package ntnu.sytemutvikling.team6.models;
-
-public class UserRegistry {
-
-
-}
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/service/AuthenticationService.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/service/AuthenticationService.java
deleted file mode 100644
index 5f98c21..0000000
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/service/AuthenticationService.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package ntnu.sytemutvikling.team6.service;
-
-public class AuthenticationService {
-}
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/service/CharityService.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/service/CharityService.java
deleted file mode 100644
index 6bd33ce..0000000
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/service/CharityService.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package ntnu.sytemutvikling.team6.service;
-
-public class CharityService {
-}
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/service/DonationService.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/service/DonationService.java
deleted file mode 100644
index bc4a7c8..0000000
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/service/DonationService.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package ntnu.sytemutvikling.team6.service;
-
-public class DonationService {
-}
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/service/FeedbackService.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/service/FeedbackService.java
deleted file mode 100644
index 93b2555..0000000
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/service/FeedbackService.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package ntnu.sytemutvikling.team6.service;
-
-public class FeedbackService {
-}
From ebab1f258826e40c3122e042c6cc56522aa86940 Mon Sep 17 00:00:00 2001
From: Roar
Date: Tue, 24 Feb 2026 14:09:51 +0100
Subject: [PATCH 02/77] Updated pom.xml
Added selenium as a new dependency.
---
helpmehelpapplication/pom.xml | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/helpmehelpapplication/pom.xml b/helpmehelpapplication/pom.xml
index da3d4e2..0ad37a5 100644
--- a/helpmehelpapplication/pom.xml
+++ b/helpmehelpapplication/pom.xml
@@ -25,6 +25,11 @@
javafx-controls
25.0.1
+
+ org.seleniumhq.selenium
+ selenium-java
+ 4.41.0
+
From 4d145ae3784926601f4544cae5f67aa4bf64e9b9 Mon Sep 17 00:00:00 2001
From: Roar
Date: Tue, 24 Feb 2026 14:14:16 +0100
Subject: [PATCH 03/77] Added IKOrganizationScraper class
Added class to scrape confirmed organizations from InnsamlingsKontrollen.
---
.../team6/models/IKOrganizationScraper.java | 96 +++++++++++++++++++
1 file changed, 96 insertions(+)
create mode 100644 helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/IKOrganizationScraper.java
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/IKOrganizationScraper.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/IKOrganizationScraper.java
new file mode 100644
index 0000000..cbc4b0f
--- /dev/null
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/IKOrganizationScraper.java
@@ -0,0 +1,96 @@
+package ntnu.sytemutvikling.team6.models;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.chrome.ChromeDriver;
+import org.openqa.selenium.chrome.ChromeOptions;
+import org.openqa.selenium.support.ui.ExpectedConditions;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+public class IKOrganizationScraper {
+ private final List organizationData;
+
+ public IKOrganizationScraper() {
+ this.organizationData = new ArrayList<>();
+ }
+
+ public boolean updateData() {
+ // Configure headless chrome browser
+ ChromeOptions options = new ChromeOptions();
+ options.addArguments("--headless=new");
+ options.addArguments("--window-size=1920,1080");
+ options.addArguments("--disable-gpu");
+ options.addArguments("--no-sandbox");
+ options.addArguments("--disable-dev-shm-usage");
+
+ WebDriver driver = new ChromeDriver(options);
+
+ //URL for godkjente organisasjoner
+ driver.get("https://www.innsamlingskontrollen.no/organisasjoner/");
+
+ // Wait to ensure tabular data is loaded first
+ WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
+ wait.until(ExpectedConditions.presenceOfElementLocated(By.tagName("table")));
+
+ // Loops through table rows and columns
+ List rows = driver.findElements(By.cssSelector("table tbody tr"));
+
+ String name = null;
+ String telephone = null;
+ String location = null;
+ String status = null;
+
+ // Clear old data before updating
+ this.organizationData.clear();
+
+ for (WebElement row : rows) {
+ List columns = row.findElements(By.tagName("td"));
+
+ for (int i = 0; i < columns.size(); i++) {
+
+ WebElement column = columns.get(i);
+
+ // Non-verification columns
+ if (i == 0) {
+ name = column.getText();
+ }
+
+ if (i == 1) {
+ telephone = columns.get(i).getText();
+ }
+
+ if (i == 2) {
+ location = columns.get(i).getText();
+ }
+
+ // Verification column
+ if (i == 3) {
+ if (!column.findElements(By.cssSelector(".status-pre-approved")).isEmpty()) {
+ status = "Monitored";
+ } else if (!column.findElements(By.cssSelector(".status-approved")).isEmpty()) {
+ status = "Approved";
+ } else {
+ status = "Unknown";
+ }
+ }
+ }
+
+ var organization = new Organization(name, telephone, location, status);
+ this.organizationData.add(organization);
+ }
+ driver.quit();
+
+ // Removes the first empty row that lists only the categories, aka no relevant data
+ this.organizationData.removeFirst();
+
+ return true;
+ }
+
+ public List getData() {
+ return this.organizationData;
+ }
+}
From bf7327e318c4a76c1648591a08f5cac44a136467 Mon Sep 17 00:00:00 2001
From: Roar
Date: Tue, 24 Feb 2026 14:14:52 +0100
Subject: [PATCH 04/77] Added Organization class
Added class to store values from scraped organizations.
---
.../team6/models/Organization.java | 32 +++++++++++++++++++
1 file changed, 32 insertions(+)
create mode 100644 helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Organization.java
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Organization.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Organization.java
new file mode 100644
index 0000000..600b7a9
--- /dev/null
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Organization.java
@@ -0,0 +1,32 @@
+package ntnu.sytemutvikling.team6.models;
+
+public class Organization {
+ String name;
+ String telephone;
+ String location;
+ String status;
+
+ public Organization(String name, String telephone, String location, String status) {
+ this.name = name;
+ this.telephone = telephone;
+ this.location = location;
+ this.status = status;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public String getTelephone() {
+ return this.telephone;
+ }
+
+ public String getLocation() {
+ return this.location;
+ }
+
+ public String getStatus() {
+ return this.status;
+ }
+
+}
From 7e7696a4186b910631edf38a2ffc617882b218b1 Mon Sep 17 00:00:00 2001
From: Roar
Date: Tue, 24 Feb 2026 14:37:09 +0100
Subject: [PATCH 05/77] Updated IKOrganizationScraper
Changed some of the logic in updateData() to prepare for format for writing to csv file.
---
.../team6/models/IKOrganizationScraper.java | 36 ++++++++++---------
1 file changed, 20 insertions(+), 16 deletions(-)
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/IKOrganizationScraper.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/IKOrganizationScraper.java
index cbc4b0f..c4abb75 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/IKOrganizationScraper.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/IKOrganizationScraper.java
@@ -36,9 +36,6 @@ public boolean updateData() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.presenceOfElementLocated(By.tagName("table")));
- // Loops through table rows and columns
- List rows = driver.findElements(By.cssSelector("table tbody tr"));
-
String name = null;
String telephone = null;
String location = null;
@@ -47,28 +44,38 @@ public boolean updateData() {
// Clear old data before updating
this.organizationData.clear();
- for (WebElement row : rows) {
- List columns = row.findElements(By.tagName("td"));
+ // Loops through table rows and columns
+ List rows = driver.findElements(By.cssSelector("table tbody tr"));
+
+ for (int i = 0; i < rows.size(); i++) {
+ List columns = rows.get(i).findElements(By.tagName("td"));
- for (int i = 0; i < columns.size(); i++) {
+ // Create organization with category names for csv file
+ if (i == 0) {
+ var categories = new Organization("Name", "Telephone", "Location", "Status");
+ this.organizationData.add(categories);
+ continue;
+ }
- WebElement column = columns.get(i);
+ for (int j = 0; j < columns.size(); j++) {
+
+ WebElement column = columns.get(j);
// Non-verification columns
- if (i == 0) {
+ if (j == 0) {
name = column.getText();
}
- if (i == 1) {
- telephone = columns.get(i).getText();
+ if (j == 1) {
+ telephone = columns.get(j).getText();
}
- if (i == 2) {
- location = columns.get(i).getText();
+ if (j == 2) {
+ location = columns.get(j).getText();
}
// Verification column
- if (i == 3) {
+ if (j == 3) {
if (!column.findElements(By.cssSelector(".status-pre-approved")).isEmpty()) {
status = "Monitored";
} else if (!column.findElements(By.cssSelector(".status-approved")).isEmpty()) {
@@ -84,9 +91,6 @@ public boolean updateData() {
}
driver.quit();
- // Removes the first empty row that lists only the categories, aka no relevant data
- this.organizationData.removeFirst();
-
return true;
}
From d6ed083914b2065ea15db5ae69b6197341797359 Mon Sep 17 00:00:00 2001
From: Roar
Date: Thu, 26 Feb 2026 10:57:23 +0100
Subject: [PATCH 06/77] Updated pom.xml
Added opencsv as a dependency.
---
helpmehelpapplication/pom.xml | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/helpmehelpapplication/pom.xml b/helpmehelpapplication/pom.xml
index 0ad37a5..57397c7 100644
--- a/helpmehelpapplication/pom.xml
+++ b/helpmehelpapplication/pom.xml
@@ -30,6 +30,11 @@
selenium-java
4.41.0
+
+ com.opencsv
+ opencsv
+ 5.12.0
+
From 07d0180dd3ee2c036ef403ba748a8cfe6c268d0c Mon Sep 17 00:00:00 2001
From: Roar
Date: Thu, 26 Feb 2026 10:59:11 +0100
Subject: [PATCH 07/77] Updated IKOrganizationScraper
Changed the logic for updateData to be more robust (old method didn't get all data reliably). Put method in a try - finally statement to ensure headless chrome browser reliably shuts down after completion/if error.
Added a method to write gathered data to a csv file.
Made the getter method return an immutable list.
---
.../team6/models/IKOrganizationScraper.java | 154 +++++++++---------
1 file changed, 78 insertions(+), 76 deletions(-)
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/IKOrganizationScraper.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/IKOrganizationScraper.java
index c4abb75..d7e833d 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/IKOrganizationScraper.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/IKOrganizationScraper.java
@@ -1,8 +1,12 @@
package ntnu.sytemutvikling.team6.models;
+import java.io.FileWriter;
+import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
+import com.opencsv.CSVWriter;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
@@ -12,89 +16,87 @@
import org.openqa.selenium.support.ui.WebDriverWait;
public class IKOrganizationScraper {
+
private final List organizationData;
+ private final String filename = "charities.csv";
public IKOrganizationScraper() {
this.organizationData = new ArrayList<>();
}
- public boolean updateData() {
- // Configure headless chrome browser
- ChromeOptions options = new ChromeOptions();
- options.addArguments("--headless=new");
- options.addArguments("--window-size=1920,1080");
- options.addArguments("--disable-gpu");
- options.addArguments("--no-sandbox");
- options.addArguments("--disable-dev-shm-usage");
-
- WebDriver driver = new ChromeDriver(options);
-
- //URL for godkjente organisasjoner
- driver.get("https://www.innsamlingskontrollen.no/organisasjoner/");
-
- // Wait to ensure tabular data is loaded first
- WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
- wait.until(ExpectedConditions.presenceOfElementLocated(By.tagName("table")));
-
- String name = null;
- String telephone = null;
- String location = null;
- String status = null;
-
- // Clear old data before updating
- this.organizationData.clear();
-
- // Loops through table rows and columns
- List rows = driver.findElements(By.cssSelector("table tbody tr"));
-
- for (int i = 0; i < rows.size(); i++) {
- List columns = rows.get(i).findElements(By.tagName("td"));
-
- // Create organization with category names for csv file
- if (i == 0) {
- var categories = new Organization("Name", "Telephone", "Location", "Status");
- this.organizationData.add(categories);
- continue;
- }
-
- for (int j = 0; j < columns.size(); j++) {
-
- WebElement column = columns.get(j);
-
- // Non-verification columns
- if (j == 0) {
- name = column.getText();
- }
-
- if (j == 1) {
- telephone = columns.get(j).getText();
- }
-
- if (j == 2) {
- location = columns.get(j).getText();
- }
-
- // Verification column
- if (j == 3) {
- if (!column.findElements(By.cssSelector(".status-pre-approved")).isEmpty()) {
- status = "Monitored";
- } else if (!column.findElements(By.cssSelector(".status-approved")).isEmpty()) {
- status = "Approved";
- } else {
- status = "Unknown";
- }
+ public boolean updateData() {
+ // Configure headless Chrome browser
+ ChromeOptions options = new ChromeOptions();
+ options.addArguments("--headless=new");
+ options.addArguments("--window-size=1920,1080");
+ options.addArguments("--disable-gpu");
+ options.addArguments("--no-sandbox");
+ options.addArguments("--disable-dev-shm-usage");
+
+ WebDriver driver = new ChromeDriver(options);
+
+ try {
+ //URL for scraping approved organizations
+ driver.get("https://www.innsamlingskontrollen.no/organisasjoner/");
+
+ WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
+ wait.until(ExpectedConditions.numberOfElementsToBeMoreThan(By.cssSelector(
+ "table tbody tr"), 0));
+ List rows = driver.findElements(By.cssSelector("table tbody tr"));
+
+ if (!rows.isEmpty()) {
+ wait.until(ExpectedConditions.visibilityOf(rows.getLast()));
+ }
+
+ // Clear old data
+ this.organizationData.clear();
+
+ // Add CSV header
+ this.organizationData.add(new Organization(
+ "Name", "Telephone", "Location", "Status"));
+
+ // Loop through table rows
+ for (WebElement row : rows) {
+ List columns = row.findElements(By.tagName("td"));
+ if (columns.size() < 4) continue;
+
+ String name = columns.get(0).getText();
+ String telephone = columns.get(1).getText();
+ String location = columns.get(2).getText();
+
+ WebElement statusColumn = columns.get(3);
+
+ String status;
+ if (!statusColumn.findElements(By.cssSelector(".status-pre-approved")).isEmpty()) {
+ status = "Monitored";
+ } else if (!statusColumn.findElements(By.cssSelector(".status-approved")).isEmpty()) {
+ status = "Approved";
+ } else {
+ status = "Unknown";
+ }
+ this.organizationData.add(new Organization(name, telephone, location, status));
+ }
+ } finally {
+ driver.quit();
}
- }
-
- var organization = new Organization(name, telephone, location, status);
- this.organizationData.add(organization);
+ return true;
}
- driver.quit();
- return true;
- }
+ public boolean writeToCSV() throws IOException {
+ try (CSVWriter writer = new CSVWriter(new FileWriter(filename))) {
+ for (Organization o : this.organizationData) {
+ writer.writeNext(new String[]{
+ o.getName(),
+ o.getTelephone(),
+ o.getLocation(),
+ o.getStatus()
+ });
+ }
+ }
+ return true;
+ }
- public List getData() {
- return this.organizationData;
- }
-}
+ public List getData() {
+ return Collections.unmodifiableList(this.organizationData);
+ }
+}
\ No newline at end of file
From 9121be598097b1804afaecfe7b4f6a324adffc62 Mon Sep 17 00:00:00 2001
From: Roar
Date: Thu, 26 Feb 2026 11:06:53 +0100
Subject: [PATCH 08/77] Updated IKOrganizationScraper
Added method deleteCSV that deletes the csv if it exists.
Added method to writeToCSV for cleaner generation of csv. Also added exceptions to handle missing data and failure to delete CSV file.
---
.../team6/models/IKOrganizationScraper.java | 21 +++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/IKOrganizationScraper.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/IKOrganizationScraper.java
index d7e833d..e43eef0 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/IKOrganizationScraper.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/IKOrganizationScraper.java
@@ -1,5 +1,6 @@
package ntnu.sytemutvikling.team6.models;
+import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.time.Duration;
@@ -83,6 +84,15 @@ public boolean updateData() {
}
public boolean writeToCSV() throws IOException {
+ if (this.organizationData.isEmpty()) {
+ throw new IllegalArgumentException("There is no data in the " +
+ "organizationData list. Run .updateData before proceeding.");
+ }
+
+ if (!this.deleteCSV()) {
+ throw new IOException("Failed to delete the CSV file.");
+ }
+
try (CSVWriter writer = new CSVWriter(new FileWriter(filename))) {
for (Organization o : this.organizationData) {
writer.writeNext(new String[]{
@@ -96,6 +106,17 @@ public boolean writeToCSV() throws IOException {
return true;
}
+ public boolean deleteCSV() {
+ var file = new File(filename);
+
+ // Returns true if file is deleted (or if file doesn't exist
+ // Returns false if file couldn't be deleted
+ if (file.exists()) {
+ return file.delete();
+ }
+ return true;
+ }
+
public List getData() {
return Collections.unmodifiableList(this.organizationData);
}
From 13c07eaf7ae9f882f4c7207e79d18572ae4a36c8 Mon Sep 17 00:00:00 2001
From: Roar
Date: Thu, 26 Feb 2026 11:11:05 +0100
Subject: [PATCH 09/77] Updated Organization
Changed parameters to private immutable parameters.
---
.../ntnu/sytemutvikling/team6/models/Organization.java | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Organization.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Organization.java
index 600b7a9..d6ab8bc 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Organization.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Organization.java
@@ -1,10 +1,10 @@
package ntnu.sytemutvikling.team6.models;
public class Organization {
- String name;
- String telephone;
- String location;
- String status;
+ private final String name;
+ private final String telephone;
+ private final String location;
+ private final String status;
public Organization(String name, String telephone, String location, String status) {
this.name = name;
From 8b7adc28c6077bf273b4f9bc81f98c1e863f43cc Mon Sep 17 00:00:00 2001
From: Roar
Date: Thu, 26 Feb 2026 11:28:04 +0100
Subject: [PATCH 10/77] Added OrganizationTest test class.
Quick test class to check that the Organization object is created successfully with correct parameters.
---
.../team6/models/OrganizationTest.java | 24 +++++++++++++++++++
1 file changed, 24 insertions(+)
create mode 100644 helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/OrganizationTest.java
diff --git a/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/OrganizationTest.java b/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/OrganizationTest.java
new file mode 100644
index 0000000..2a4d5d6
--- /dev/null
+++ b/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/OrganizationTest.java
@@ -0,0 +1,24 @@
+package ntnu.sytemutvikling.team6.models;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class OrganizationTest {
+
+ @Test
+ void initialParametersShouldBeCorrect() {
+ String name = "Hjelpesenter";
+ String telephone = "123456789";
+ String location = "London";
+ String status = "Approved";
+
+ var org = new Organization(name, telephone, location, status);
+
+ assertEquals(name, org.getName(), "Name parameter should be correct.");
+ assertEquals(telephone, org.getTelephone(), "Telephone parameter should be correct.");
+ assertEquals(location, org.getLocation(), "Location parameter should be correct.");
+ assertEquals(status, org.getStatus(), "Status parameter should be correct.");
+ }
+
+}
\ No newline at end of file
From 675612f0b10f652d2cbf03c9f1ef3e5f74b65341 Mon Sep 17 00:00:00 2001
From: Roar
Date: Fri, 27 Feb 2026 09:30:17 +0100
Subject: [PATCH 11/77] Updated pom.xml
Added GSON as a Maven dependency.
---
helpmehelpapplication/pom.xml | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/helpmehelpapplication/pom.xml b/helpmehelpapplication/pom.xml
index 57397c7..4a67e62 100644
--- a/helpmehelpapplication/pom.xml
+++ b/helpmehelpapplication/pom.xml
@@ -35,6 +35,11 @@
opencsv
5.12.0
+
+ com.google.code.gson
+ gson
+ 2.13.2
+
From f26ab51fac4a55eba04b028716c05fc4dcd7cdc8 Mon Sep 17 00:00:00 2001
From: Roar
Date: Fri, 27 Feb 2026 10:20:38 +0100
Subject: [PATCH 12/77] Added APICharityScraper & APICharityData class.
Added a class for scraping the IK API and parsing the JSON values, pushing them to a APICharityData object for easy migrating to a database later.
---
.../team6/models/APICharityData.java | 37 +++++++++++
.../team6/models/APICharityScraper.java | 63 +++++++++++++++++++
2 files changed, 100 insertions(+)
create mode 100644 helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityData.java
create mode 100644 helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityScraper.java
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityData.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityData.java
new file mode 100644
index 0000000..d94db88
--- /dev/null
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityData.java
@@ -0,0 +1,37 @@
+package ntnu.sytemutvikling.team6.models;
+
+public class APICharityData {
+ private final String org_number;
+ private final String name;
+ private final String status;
+ private final String url;
+ private final boolean is_pre_approved;
+
+ public APICharityData(String org_number, String name, String status, String url, boolean is_pre_approved) {
+ this.org_number = org_number;
+ this.name = name;
+ this.status = status;
+ this.url = url;
+ this.is_pre_approved = is_pre_approved;
+ }
+
+ public String getOrg_number() {
+ return this.org_number;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public boolean getIs_pre_approved() {
+ return this.is_pre_approved;
+ }
+}
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityScraper.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityScraper.java
new file mode 100644
index 0000000..27f2e60
--- /dev/null
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityScraper.java
@@ -0,0 +1,63 @@
+package ntnu.sytemutvikling.team6.models;
+
+import com.google.gson.Gson;
+
+import java.io.IOException;
+import java.net.*;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class APICharityScraper {
+ private final URL url;
+ private HttpURLConnection connection;
+ private final HttpClient client;
+ private final HttpRequest request;
+
+ public APICharityScraper() throws IOException, URISyntaxException {
+ this.url = new URI("https://app.innsamlingskontrollen.no/api/public/v1/all").toURL();
+ this.client = HttpClient.newHttpClient();
+ this.connection = (HttpURLConnection) url.openConnection();
+ this.request = HttpRequest.newBuilder()
+ .uri(url.toURI())
+ .GET()
+ .build();
+ }
+
+ // Checks whether a connection returns a get response
+ public boolean checkConnection() throws IOException {
+ this.connection = (HttpURLConnection) url.openConnection();
+ this.connection.setRequestMethod("GET");
+ this.connection.connect();
+
+ int response = this.connection.getResponseCode();
+ String response_text = this.connection.getResponseMessage();
+
+ if (response == 200) {
+ return true;
+ }
+ throw new RuntimeException("Connection failed : HTTP error code : " + response + ". \n Response text: " +
+ response_text);
+ }
+
+ public String getJSONData() throws IOException, InterruptedException {
+ // Gets the JSON data and stores it in response body
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+
+ return response.body();
+ }
+ public List parseJSON(String JSONData) {
+ Gson gson = new Gson();
+ APICharityData[] charityData = gson.fromJson(JSONData, APICharityData[].class);
+
+ if (charityData == null) {
+ return new ArrayList<>();
+ }
+ // Returns mutable list of JSON data converted to APICharityData Objects
+ return new ArrayList<>(Arrays.asList(charityData));
+ }
+
+}
From ebe15244c7d031e0eef080cb9d716bda0d349c25 Mon Sep 17 00:00:00 2001
From: Roar
Date: Fri, 27 Feb 2026 10:24:35 +0100
Subject: [PATCH 13/77] Added APICharityScraperTest test class.
Added testclass for the scraper with some test methods.
---
.../team6/models/APICharityScraperTest.java | 39 +++++++++++++++++++
1 file changed, 39 insertions(+)
create mode 100644 helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/APICharityScraperTest.java
diff --git a/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/APICharityScraperTest.java b/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/APICharityScraperTest.java
new file mode 100644
index 0000000..39d7487
--- /dev/null
+++ b/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/APICharityScraperTest.java
@@ -0,0 +1,39 @@
+package ntnu.sytemutvikling.team6.models;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class APICharityScraperTest {
+ @Test
+ void connectionToDataBaseShouldReturnTrue() throws IOException, URISyntaxException {
+ var con = new APICharityScraper();
+
+ assertTrue(con.checkConnection(), "Get response should be 200 (success)");
+ }
+
+
+ @Test
+ void ParsedJSONShouldHaveCorrectValues() throws IOException, URISyntaxException {
+ var con = new APICharityScraper();
+ String JSONData = "[{\"org_number\":\"938419264\",\"name\":\"Misjonsalliansen\",\"status\":\"approved\"," +
+ "\"url\":\"https:\\/\\/www.innsamlingskontrollen.no\\/organisasjoner\\/misjonsalliansen\\/\"," +
+ "\"is_pre_approved\":false}]";
+ var list = con.parseJSON(JSONData);
+
+ assertEquals("938419264", list.getFirst().getOrg_number(),
+ "Parsed organization number should be correct.");
+ assertEquals("Misjonsalliansen", list.getFirst().getName(),
+ "Parsed name should be correct");
+ assertEquals("approved", list.getFirst().getStatus(),
+ "Parsed status should be correct.");
+ assertEquals("https://www.innsamlingskontrollen.no/organisasjoner/misjonsalliansen/",
+ list.getFirst().getUrl(), "Parsed url should be correct.");
+ assertFalse(list.getFirst().getIs_pre_approved(), "Parsed is_pre_approved boolean should " +
+ "be correct.");
+ }
+
+}
\ No newline at end of file
From f6cfff807393afc5e01d5a34d63bb5ab05be92ef Mon Sep 17 00:00:00 2001
From: Roar
Date: Tue, 3 Mar 2026 12:06:15 +0100
Subject: [PATCH 14/77] Updated pom.xml.
Added mockito as a dependency to make testing of http requests easier.
---
helpmehelpapplication/pom.xml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/helpmehelpapplication/pom.xml b/helpmehelpapplication/pom.xml
index 4a67e62..863b97a 100644
--- a/helpmehelpapplication/pom.xml
+++ b/helpmehelpapplication/pom.xml
@@ -40,6 +40,12 @@
gson
2.13.2
+
+ org.mockito
+ mockito-core
+ 5.21.0
+ test
+
From 9ebb4762b0e9ff54da20f95773378dc931999ee0 Mon Sep 17 00:00:00 2001
From: Roar
Date: Tue, 3 Mar 2026 12:10:40 +0100
Subject: [PATCH 15/77] Updated APICharityScraper.
Replaced the connection/get request logic to make it easier to mocktest with mockito.
---
.../team6/models/APICharityScraper.java | 29 +++++++------------
1 file changed, 11 insertions(+), 18 deletions(-)
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityScraper.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityScraper.java
index 27f2e60..8e93637 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityScraper.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityScraper.java
@@ -1,7 +1,6 @@
package ntnu.sytemutvikling.team6.models;
import com.google.gson.Gson;
-
import java.io.IOException;
import java.net.*;
import java.net.http.HttpClient;
@@ -12,35 +11,28 @@
import java.util.List;
public class APICharityScraper {
- private final URL url;
- private HttpURLConnection connection;
private final HttpClient client;
private final HttpRequest request;
- public APICharityScraper() throws IOException, URISyntaxException {
- this.url = new URI("https://app.innsamlingskontrollen.no/api/public/v1/all").toURL();
- this.client = HttpClient.newHttpClient();
- this.connection = (HttpURLConnection) url.openConnection();
+
+ public APICharityScraper(HttpClient client) throws URISyntaxException {
+ this.client = client;
this.request = HttpRequest.newBuilder()
- .uri(url.toURI())
+ .uri(new URI("https://app.innsamlingskontrollen.no/api/public/v1/all"))
.GET()
.build();
}
// Checks whether a connection returns a get response
- public boolean checkConnection() throws IOException {
- this.connection = (HttpURLConnection) url.openConnection();
- this.connection.setRequestMethod("GET");
- this.connection.connect();
-
- int response = this.connection.getResponseCode();
- String response_text = this.connection.getResponseMessage();
+ public boolean checkConnection() throws IOException, InterruptedException {
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
- if (response == 200) {
+ if (response.statusCode() == 200) {
return true;
}
- throw new RuntimeException("Connection failed : HTTP error code : " + response + ". \n Response text: " +
- response_text);
+ throw new RuntimeException("Connection failed: HTTP error code: " + response.statusCode() +
+ "\nResponse text: " + response.body()
+ );
}
public String getJSONData() throws IOException, InterruptedException {
@@ -49,6 +41,7 @@ public String getJSONData() throws IOException, InterruptedException {
return response.body();
}
+
public List parseJSON(String JSONData) {
Gson gson = new Gson();
APICharityData[] charityData = gson.fromJson(JSONData, APICharityData[].class);
From 61520b04c0f4604f10f2ef81d6e516dcbdb15f3b Mon Sep 17 00:00:00 2001
From: Roar
Date: Tue, 3 Mar 2026 12:13:13 +0100
Subject: [PATCH 16/77] Updated APICharityScraperTest.
Changed test to use mocked http-responses using mockito for more reliable testing.
---
.../team6/models/APICharityScraperTest.java | 90 +++++++++++++++----
1 file changed, 72 insertions(+), 18 deletions(-)
diff --git a/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/APICharityScraperTest.java b/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/APICharityScraperTest.java
index 39d7487..515e616 100644
--- a/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/APICharityScraperTest.java
+++ b/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/APICharityScraperTest.java
@@ -1,39 +1,93 @@
package ntnu.sytemutvikling.team6.models;
import org.junit.jupiter.api.Test;
-
import java.io.IOException;
import java.net.URISyntaxException;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.util.List;
+
+import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
class APICharityScraperTest {
@Test
- void connectionToDataBaseShouldReturnTrue() throws IOException, URISyntaxException {
- var con = new APICharityScraper();
+ void checkConnectionShouldReturnTrueOn200ResponseCode() throws IOException, URISyntaxException, InterruptedException {
+ HttpClient mockClient = mock(HttpClient.class);
+ var mockResponse = mock(HttpResponse.class);
+
+ when(mockResponse.statusCode()).thenReturn(200);
+ when(mockClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandler.class))).thenReturn(mockResponse);
+
+ var con = new APICharityScraper(mockClient);
assertTrue(con.checkConnection(), "Get response should be 200 (success)");
}
+ @Test
+ void checkConnectionShouldThrowRuntimeExceptionIfConnectionIsNotASuccess() throws URISyntaxException, IOException, InterruptedException {
+ var mockClient = mock(HttpClient.class);
+ HttpResponse mockResponse = mock(HttpResponse.class);
+
+ when(mockResponse.statusCode()).thenReturn(500);
+ when(mockResponse.body()).thenReturn("Internal server error.");
+ when(mockClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandler.class))).thenReturn(mockResponse);
+
+ var con = new APICharityScraper(mockClient);
+
+ assertThrows(RuntimeException.class, con::checkConnection,
+ "The connection is not successful and should throw a RuntimeException.");
+ }
@Test
- void ParsedJSONShouldHaveCorrectValues() throws IOException, URISyntaxException {
- var con = new APICharityScraper();
- String JSONData = "[{\"org_number\":\"938419264\",\"name\":\"Misjonsalliansen\",\"status\":\"approved\"," +
+ void getJSONDataShouldReturnCorrectJSONString() throws IOException, InterruptedException, URISyntaxException {
+ String response = "[{\"org_number\":\"938419264\",\"name\":\"Misjonsalliansen\",\"status\":\"approved\"," +
"\"url\":\"https:\\/\\/www.innsamlingskontrollen.no\\/organisasjoner\\/misjonsalliansen\\/\"," +
"\"is_pre_approved\":false}]";
- var list = con.parseJSON(JSONData);
-
- assertEquals("938419264", list.getFirst().getOrg_number(),
- "Parsed organization number should be correct.");
- assertEquals("Misjonsalliansen", list.getFirst().getName(),
- "Parsed name should be correct");
- assertEquals("approved", list.getFirst().getStatus(),
- "Parsed status should be correct.");
- assertEquals("https://www.innsamlingskontrollen.no/organisasjoner/misjonsalliansen/",
- list.getFirst().getUrl(), "Parsed url should be correct.");
- assertFalse(list.getFirst().getIs_pre_approved(), "Parsed is_pre_approved boolean should " +
- "be correct.");
+
+ var mockClient = mock(HttpClient.class);
+ var mockResponse = mock(HttpResponse.class);
+
+ when(mockResponse.body()).thenReturn(response);
+ when(mockClient.send(any(HttpRequest.class), any(HttpResponse.BodyHandler.class))).thenReturn(mockResponse);
+
+ var con = new APICharityScraper(mockClient);
+
+ assertEquals(response, con.getJSONData(), "The extracted JSON data should be the same " +
+ "as the data from the API.");
+ }
+
+
+ @Test
+ void parsedJSONShouldHaveCorrectValues() throws URISyntaxException {
+ APICharityScraper con = new APICharityScraper(HttpClient.newHttpClient());
+
+ String JSONData = "[{\"org_number\":\"938419264\",\"name\":\"Misjonsalliansen\",\"status\":\"approved\"," +
+ "\"url\":\"https://www.innsamlingskontrollen.no/organisasjoner/misjonsalliansen/\",\"" +
+ "is_pre_approved\":false}]";
+ List list = con.parseJSON(JSONData);
+
+ APICharityData d = list.getFirst();
+
+ assertEquals("938419264", d.getOrg_number(), "Org_number parameter " +
+ "should be correct.");
+ assertEquals("Misjonsalliansen", d.getName(), "Name parameter should be correct.");
+ assertEquals("approved", d.getStatus(), "Status parameter should be correct.");
+ assertEquals("https://www.innsamlingskontrollen.no/organisasjoner/misjonsalliansen/", d.getUrl(),
+ "Url parameter should be correct.");
+ assertFalse(d.getIs_pre_approved(), "Is_pre_approved parameter should be correct.");
+ }
+
+ @Test
+ void parsedJSONOfNullShouldReturnEmptyList() throws URISyntaxException {
+ APICharityScraper con = new APICharityScraper(HttpClient.newHttpClient());
+
+ List list = con.parseJSON(null);
+
+ assertTrue(list.isEmpty(), "List should be empty due to only parsing " +
+ "null objects.");
}
}
\ No newline at end of file
From 01929705b96c2f412373bc788e579720d04f9443 Mon Sep 17 00:00:00 2001
From: Roar
Date: Tue, 3 Mar 2026 12:14:01 +0100
Subject: [PATCH 17/77] Added APICharityDataTest
Added test for constructor/getters for ApiCharityData.
---
.../team6/models/APICharityDataTest.java | 32 +++++++++++++++++++
1 file changed, 32 insertions(+)
create mode 100644 helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/APICharityDataTest.java
diff --git a/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/APICharityDataTest.java b/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/APICharityDataTest.java
new file mode 100644
index 0000000..c863686
--- /dev/null
+++ b/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/APICharityDataTest.java
@@ -0,0 +1,32 @@
+package ntnu.sytemutvikling.team6.models;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class APICharityDataTest {
+
+ @Test
+ void initialParametersShouldBeCorrect() {
+ APICharityData charity = new APICharityData(
+ "938419264", "Misjonsalliansen", "approved",
+ "https://www.innsamlingskontrollen.no/organisasjoner/misjonsalliansen/", false
+ );
+
+ assertEquals("938419264", charity.getOrg_number(),
+ "Org_number parameter of APICharityData should be correct.");
+ assertEquals("Misjonsalliansen", charity.getName(),
+ "Name parameter of APICharityData should be correct.");
+ assertEquals("approved", charity.getStatus(),
+ "Status parameter of APICharityData should be correct.");
+ assertEquals("https://www.innsamlingskontrollen.no/organisasjoner/misjonsalliansen/",
+ charity.getUrl(), "Url parameter of APICharityData should be correct.");
+ assertFalse(charity.getIs_pre_approved(), "Is_pre_approved parameter of APICharityData should " +
+ "be correct.");
+
+ }
+
+
+
+
+}
\ No newline at end of file
From 8b170cf8c26d39a163777659e25a9aaaf8dc8166 Mon Sep 17 00:00:00 2001
From: Roar
Date: Wed, 4 Mar 2026 20:05:08 +0100
Subject: [PATCH 18/77] Updated pom.xml.
Added mysql-connector-j as a dependency.
---
helpmehelpapplication/pom.xml | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/helpmehelpapplication/pom.xml b/helpmehelpapplication/pom.xml
index 863b97a..e921a97 100644
--- a/helpmehelpapplication/pom.xml
+++ b/helpmehelpapplication/pom.xml
@@ -46,6 +46,11 @@
5.21.0
test
+
+ com.mysql
+ mysql-connector-j
+ 9.6.0
+
From c0efba400986c88570dfbb4b07416a79a0cd1987 Mon Sep 17 00:00:00 2001
From: Roar
Date: Wed, 4 Mar 2026 20:08:07 +0100
Subject: [PATCH 19/77] Updated APICharityScraper.
Added a streams filter to remove "obs" status values in the JSON data from the API, due to non-unique org_number values. Org_number is used as primary key, so this caused issues with database.
---
.../sytemutvikling/team6/models/APICharityScraper.java | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityScraper.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityScraper.java
index 8e93637..8bb1b1d 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityScraper.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityScraper.java
@@ -49,8 +49,11 @@ public List parseJSON(String JSONData) {
if (charityData == null) {
return new ArrayList<>();
}
- // Returns mutable list of JSON data converted to APICharityData Objects
- return new ArrayList<>(Arrays.asList(charityData));
+
+ // Filters out "obs" status due to non-unique org_number values
+ return Arrays.stream(charityData)
+ .filter(c-> !"obs".equalsIgnoreCase(c.getStatus()))
+ .toList();
}
}
From 5c8c8a649f8de84b800773a52b5caf73cba0f3d3 Mon Sep 17 00:00:00 2001
From: Roar
Date: Wed, 4 Mar 2026 20:19:44 +0100
Subject: [PATCH 20/77] Added DatabaseManager class.
Working manager class for handling creation, insertion, and deletion of tables and records. Currently, supports creating charities table, updating the table with values from the IK API, and doing a parity check, removing values that doesn't exist in the IK API.
---
.../team6/models/DatabaseManager.java | 127 ++++++++++++++++++
1 file changed, 127 insertions(+)
create mode 100644 helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/DatabaseManager.java
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/DatabaseManager.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/DatabaseManager.java
new file mode 100644
index 0000000..6d91982
--- /dev/null
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/DatabaseManager.java
@@ -0,0 +1,127 @@
+package ntnu.sytemutvikling.team6.models;
+
+import java.sql.*;
+import java.util.List;
+
+public class DatabaseManager {
+ // Database environment is MySQL
+ private final String databaseURL;
+ private final String username;
+ private final String password;
+
+ // Values stored in system environment variables for security (using ntnu's phpmyadmin for this project)
+ public DatabaseManager() {
+ this.databaseURL = System.getenv("HMH_DB_URL");
+ this.username = System.getenv("HMH_DB_USERNAME");
+ this.password = System.getenv("HMH_DB_PASSWORD");
+
+ if (this.databaseURL == null || this.databaseURL.isBlank()) {
+ throw new IllegalStateException("Database environment variable URL has not been set");
+ }
+
+ if (this.username == null || this.username.isBlank()) {
+ throw new IllegalStateException("Username environment variable has not been set");
+ }
+
+ if (this.password == null || this.password.isBlank()) {
+ throw new IllegalStateException("Password environment variable has not been set");
+ }
+ }
+
+ public void createCharitiesTable() {
+ // Creates table if it doesn't exist
+ String sql_create_main_table = """
+ CREATE TABLE IF NOT EXISTS charities (
+ org_number VARCHAR(100) PRIMARY KEY,
+ name VARCHAR(255),
+ status VARCHAR(50),
+ url VARCHAR(255),
+ is_pre_approved BOOLEAN
+ )""";
+
+ try (Connection conn = DriverManager.getConnection(databaseURL, username, password);
+ Statement s = conn.createStatement()) {
+
+ s.execute(sql_create_main_table);
+ } catch (SQLException e) {
+ throw new RuntimeException("Error creating tables.");
+ }
+ }
+
+ public void updateCharities(List charities) {
+ // Inserts values in APICharityData to table
+
+ try (Connection conn = DriverManager.getConnection(databaseURL, username, password))
+ {
+ // Inserts objects values into charities
+ String sql_update = """
+ INSERT INTO charities (org_number, name, status, url, is_pre_approved)
+ VALUES (?, ?, ?, ?, ?)
+ ON DUPLICATE KEY UPDATE
+ name = VALUES(name),
+ status = VALUES(status),
+ url = VALUES(url),
+ is_pre_approved = VALUES(is_pre_approved)
+ """;
+
+ PreparedStatement s = conn.prepareStatement(sql_update);
+
+ for (APICharityData charity : charities) {
+ s.setString(1, charity.getOrg_number().replaceAll("\\s", ""));
+ s.setString(2, charity.getName());
+ s.setString(3, charity.getStatus());
+ s.setString(4, charity.getUrl());
+ s.setBoolean(5, charity.getIs_pre_approved());
+
+ s.addBatch();
+ }
+ s.executeBatch();
+
+ // Removed temp table used for parity check
+ String sql_drop_temp_table = "DROP TABLE IF EXISTS temp";
+ PreparedStatement drop_temp_table = conn.prepareStatement(sql_drop_temp_table);
+ drop_temp_table.execute();
+
+ // Creates a temp table used for parity check
+ String sql_create_temp_table = "CREATE TABLE temp (org_number VARCHAR(100) PRIMARY KEY)";
+ PreparedStatement temp_create = conn.prepareStatement(sql_create_temp_table);
+ temp_create.execute();
+
+ // Inserts org_number into the temp table
+ String sql_update_temp_table = "INSERT INTO temp (org_number) VALUES (?)";
+ PreparedStatement temp_update = conn.prepareStatement(sql_update_temp_table);
+ for (APICharityData charity : charities) {
+ temp_update.setString(1, charity.getOrg_number().replaceAll("\\s", ""));
+ temp_update.addBatch();
+ }
+ temp_update.executeBatch();
+
+ // Removes records from main charities table, that don't exist in the temp table.
+ // This makes it so if any organization is removed by IK, it will also be removed from the database.
+ String sql_delete_old_records = """
+ DELETE FROM charities c
+ WHERE NOT EXISTS (
+ SELECT 1
+ FROM temp t
+ WHERE c.org_number = t.org_number
+ )
+ """;
+ PreparedStatement delete_old_records = conn.prepareStatement(sql_delete_old_records);
+ delete_old_records.execute();
+
+ // Deletes temporary table
+ String sql_delete_temp_table = "DROP TABLE IF EXISTS temp";
+ PreparedStatement delete_temp_table = conn.prepareStatement(sql_delete_temp_table);
+ delete_temp_table.execute();
+
+
+
+
+ } catch (SQLException e) {
+ // prints whole sql error message
+ e.printStackTrace();
+ throw new RuntimeException("ERROR: Something went wrong during updating charities table.");
+ }
+
+ }
+}
\ No newline at end of file
From 9cb3206d33d76bdde7eb8a95f613fac2c01abb97 Mon Sep 17 00:00:00 2001
From: Roar
Date: Wed, 4 Mar 2026 20:45:46 +0100
Subject: [PATCH 21/77] Updated APICharityData
Added javadoc descriptions, throws an exception if org_number is invalid, and automatically normalizes the value of org_number.
---
.../team6/models/APICharityData.java | 57 ++++++++++++++++++-
1 file changed, 56 insertions(+), 1 deletion(-)
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityData.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityData.java
index d94db88..10f3acf 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityData.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityData.java
@@ -1,5 +1,17 @@
package ntnu.sytemutvikling.team6.models;
+/**
+ * Represents data parsed from the IK API JSON response.
+ * Instances are immutable; to update any value, a new object must be created.
+ *
+ * Receives data directly from {@link APICharityScraper} or {@link IKOrganizationScraper}.
+ *
+ *
+ * {@code org_number} should be a unique number, as it is used as a primary key
+ * in {@link DatabaseManager}.
+ *
+ */
+
public class APICharityData {
private final String org_number;
private final String name;
@@ -7,30 +19,73 @@ public class APICharityData {
private final String url;
private final boolean is_pre_approved;
+ /**
+ * Constructs a new APICharityData object.
+ * @param org_number a unique number that identifies the organization
+ * @param name the name of the organization
+ * @param status {@code approved} for approved organizations,
+ * {@code obs} for non-approved organizations
+ * @param url the URL for more info about the organization on the IK domain
+ * @param is_pre_approved whether the organization was pre-approved
+ */
+
public APICharityData(String org_number, String name, String status, String url, boolean is_pre_approved) {
- this.org_number = org_number;
+ if (org_number == null || org_number.isBlank()) {
+ throw new IllegalArgumentException("ERROR: Org number cannot be null or blank");
+ }
+ this.org_number = org_number.replaceAll("\\s", "");
this.name = name;
this.status = status;
this.url = url;
this.is_pre_approved = is_pre_approved;
}
+ /**
+ * Returns the organization number. Must not be {@code null} or blank.
+ *
+ * @return the organization number
+ */
+
public String getOrg_number() {
return this.org_number;
}
+ /**
+ * Returns the name of the organization. Whitespace removed.
+ *
+ * @return the name of the organization
+ */
+
public String getName() {
return name;
}
+ /**
+ * Returns whether the organization is approved or not
+ * @return the approved status of the organization
+ */
+
public String getStatus() {
return status;
}
+ /**
+ * Returns the URL of the organizations information page on IK
+ *
+ * @return the URL for more info about the organization
+ */
+
public String getUrl() {
return url;
}
+ /**
+ * Returns whether the organization was pre-approved.
+ *
+ * @return {@code true} if organization was pre-approved
+ * {@code false} otherwise
+ */
+
public boolean getIs_pre_approved() {
return this.is_pre_approved;
}
From a723cedda1b6f61891a73c4bb0f0d9b7fcc7ff11 Mon Sep 17 00:00:00 2001
From: Roar
Date: Wed, 4 Mar 2026 21:01:39 +0100
Subject: [PATCH 22/77] Updated APICharityDataTest
Added tests for new logic regarding org_number.
---
.../team6/models/APICharityDataTest.java | 33 +++++++++++++++++++
1 file changed, 33 insertions(+)
diff --git a/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/APICharityDataTest.java b/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/APICharityDataTest.java
index c863686..a015ac1 100644
--- a/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/APICharityDataTest.java
+++ b/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/APICharityDataTest.java
@@ -26,6 +26,39 @@ void initialParametersShouldBeCorrect() {
}
+ @Test
+ void org_numberWhiteSpaceShouldBeremoved() {
+ String org_number = "938 4192 64";
+ APICharityData charity = new APICharityData(
+ org_number, "Misjonsalliansen", "approved",
+ "https://www.innsamlingskontrollen.no/organisasjoner/misjonsalliansen/", false
+ );
+
+ assertEquals(org_number.replaceAll("\\s",""), charity.getOrg_number(),
+ "Org_number should not contain whitespace.");
+ }
+
+ @Test
+ void nullOrg_numberShouldThrowIllegalArgumentException() {
+ assertThrows(IllegalArgumentException.class, () -> new APICharityData(null, "Misjonsalliansen",
+ "approved", "https://www.innsamlingskontrollen.no/organisasjoner/misjonsalliansen/",
+ false), "Null org_number should not be allowed.");
+ }
+
+ @Test
+ void blankOrg_numberShouldThrowIllegalArgumentException() {
+ assertThrows(IllegalArgumentException.class, () -> new APICharityData(" ", "Misjonsalliansen",
+ "approved", "https://www.innsamlingskontrollen.no/organisasjoner/misjonsalliansen/",
+ false), "Blank org_number should not be allowed.");
+ }
+
+ @Test
+ void emptyOrg_numberShouldThrowIllegalArgumentException() {
+ assertThrows(IllegalArgumentException.class, () -> new APICharityData("", "Misjonsalliansen",
+ "approved", "https://www.innsamlingskontrollen.no/organisasjoner/misjonsalliansen/",
+ false), "Null org_number should not be allowed.");
+ }
+
From 636351e20b725d5647844beaf38271e18740283e Mon Sep 17 00:00:00 2001
From: Roar
Date: Wed, 4 Mar 2026 21:22:34 +0100
Subject: [PATCH 23/77] Updated APICharityScraper
Added JavaDocs.
---
.../team6/models/APICharityScraper.java | 36 +++++++++++++++++--
1 file changed, 34 insertions(+), 2 deletions(-)
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityScraper.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityScraper.java
index 8bb1b1d..e92baca 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityScraper.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityScraper.java
@@ -10,10 +10,19 @@
import java.util.Arrays;
import java.util.List;
+/**
+ * Fetches JSON information from the IK API and parses the JSON into a list of {@link APICharityData} objects.
+ */
+
public class APICharityScraper {
private final HttpClient client;
private final HttpRequest request;
+ /**
+ * Constructs a new APICharityScraper object.
+ * @param client the client responsible for making the http connection.
+ * @throws URISyntaxException if the API URL is malformed or violates URI syntax rules
+ */
public APICharityScraper(HttpClient client) throws URISyntaxException {
this.client = client;
@@ -23,7 +32,15 @@ public APICharityScraper(HttpClient client) throws URISyntaxException {
.build();
}
- // Checks whether a connection returns a get response
+ /**
+ * Checks if the http request returns an 'OK' response.
+ *
+ * @return {@code true} if connection was successful
+ * @throws IOException if an I/O error occurs while sending or receiving the HTTP reques
+ * @throws InterruptedException if the operation is interrupted while waiting for the response
+ * @throws RuntimeException if the connection is unsuccessful
+ */
+
public boolean checkConnection() throws IOException, InterruptedException {
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
@@ -35,13 +52,28 @@ public boolean checkConnection() throws IOException, InterruptedException {
);
}
+ /**
+ * Fetches the JSON data from the IK API and stores it in a String.
+ *
+ * @return a String of the JSON values in IK API
+ * @throws IOException if an I/O error occurs while sending or receiving the HTTP request
+ * @throws InterruptedException if the operation is interrupted while waiting for the response
+ */
+
public String getJSONData() throws IOException, InterruptedException {
- // Gets the JSON data and stores it in response body
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
return response.body();
}
+ /**
+ * Parses the JSON data using gson and creates a list of APICharityData objects.
+ *
+ * @param JSONData the {@code String} of JSON data to be parsed
+ * @return a list of APICharityDaya objects
+ * @throws com.google.gson.JsonSyntaxException if the provided JSON is not valid
+ */
+
public List parseJSON(String JSONData) {
Gson gson = new Gson();
APICharityData[] charityData = gson.fromJson(JSONData, APICharityData[].class);
From e99d6c72beb8207fbed0efa8793beb6f42f757fb Mon Sep 17 00:00:00 2001
From: Roar
Date: Wed, 4 Mar 2026 21:23:39 +0100
Subject: [PATCH 24/77] Updated APICharityScraperTest
Added a test to check that JSON entries with status:'obs' gets ignored.
---
.../team6/models/APICharityScraperTest.java | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/APICharityScraperTest.java b/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/APICharityScraperTest.java
index 515e616..e47431f 100644
--- a/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/APICharityScraperTest.java
+++ b/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/APICharityScraperTest.java
@@ -90,4 +90,18 @@ void parsedJSONOfNullShouldReturnEmptyList() throws URISyntaxException {
"null objects.");
}
+ @Test
+ void shouldRemoveObsStatusEntries() throws URISyntaxException {
+ APICharityScraper con = new APICharityScraper(HttpClient.newHttpClient());
+
+ String JSONData = "[{\"org_number\":\"938419264\",\"name\":\"Misjonsalliansen\",\"status\":\"obs\"," +
+ "\"url\":\"https://www.innsamlingskontrollen.no/organisasjoner/misjonsalliansen/\",\"" +
+ "is_pre_approved\":false}]";
+ List list = con.parseJSON(JSONData);
+
+ assertEquals(0, list.size(),"Entries containing 'obs' should be ignored, so list should " +
+ "be empty.");
+
+ }
+
}
\ No newline at end of file
From c6ee1c9e4ffb6481d791d6b9993badfe7ef0b7ed Mon Sep 17 00:00:00 2001
From: Roar
Date: Wed, 4 Mar 2026 22:43:07 +0100
Subject: [PATCH 25/77] Updated DatabaseManager
Added JavaDocs and printStackTrace to createCharitiesTable method.
---
.../team6/models/DatabaseManager.java | 59 ++++++++++++++++---
1 file changed, 50 insertions(+), 9 deletions(-)
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/DatabaseManager.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/DatabaseManager.java
index 6d91982..a4e2ce8 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/DatabaseManager.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/DatabaseManager.java
@@ -3,12 +3,34 @@
import java.sql.*;
import java.util.List;
+/**
+ * Manages creation and synchronization of database tables in a MySQL database.
+ *
+ * This class is responsible for establishing connections using environment variables
+ * and maintaining the {@code charities} table based on data retrieved from the IK API.
+ *
+ */
+
public class DatabaseManager {
- // Database environment is MySQL
private final String databaseURL;
private final String username;
private final String password;
+ /**
+ Constructs a new {@code DatabaseManager} using database credentials
+ * retrieved from system environment variables.
+ *
+ * Required environment variables:
+ *
+ * - {@code HMH_DB_URL}
+ * - {@code HMH_DB_USERNAME}
+ * - {@code HMH_DB_PASSWORD}
+ *
+ *
+ *
+ * @throws IllegalStateException if either databaseURL, username, or password is {@code null} or blank
+ */
+
// Values stored in system environment variables for security (using ntnu's phpmyadmin for this project)
public DatabaseManager() {
this.databaseURL = System.getenv("HMH_DB_URL");
@@ -28,8 +50,15 @@ public DatabaseManager() {
}
}
+ /**
+ * Creates the {@code charities} table if it does not already exist.
+ *
+ * The table structure is based on fields from {@link APICharityData}.
+ *
+ * @throws RuntimeException if a {@link SQLException} occurs while creating the table
+ */
+
public void createCharitiesTable() {
- // Creates table if it doesn't exist
String sql_create_main_table = """
CREATE TABLE IF NOT EXISTS charities (
org_number VARCHAR(100) PRIMARY KEY,
@@ -44,12 +73,28 @@ url VARCHAR(255),
s.execute(sql_create_main_table);
} catch (SQLException e) {
+ e.printStackTrace();
throw new RuntimeException("Error creating tables.");
}
}
+ /**
+ * Synchronizes the {@code charities} table with the provided list of APICharityData.
+ *
+ * Summary of function:
+ *
+ * - Inserts new records
+ * - Updates existing records using {@code ON DUPLICATE KEY UPDATE}
+ * - Removes database records that are not present in the provided list
+ *
+ * A temporary table is used to determine which records should be deleted.
+ *
+ *
+ * @param charities a list of ApiCharityDaya objects
+ * @throws RuntimeException if a {@link SQLException} occurs during database synchronization
+ */
+
public void updateCharities(List charities) {
- // Inserts values in APICharityData to table
try (Connection conn = DriverManager.getConnection(databaseURL, username, password))
{
@@ -77,17 +122,15 @@ INSERT INTO charities (org_number, name, status, url, is_pre_approved)
}
s.executeBatch();
- // Removed temp table used for parity check
+ // Temporary table for parity check
String sql_drop_temp_table = "DROP TABLE IF EXISTS temp";
PreparedStatement drop_temp_table = conn.prepareStatement(sql_drop_temp_table);
drop_temp_table.execute();
- // Creates a temp table used for parity check
String sql_create_temp_table = "CREATE TABLE temp (org_number VARCHAR(100) PRIMARY KEY)";
PreparedStatement temp_create = conn.prepareStatement(sql_create_temp_table);
temp_create.execute();
- // Inserts org_number into the temp table
String sql_update_temp_table = "INSERT INTO temp (org_number) VALUES (?)";
PreparedStatement temp_update = conn.prepareStatement(sql_update_temp_table);
for (APICharityData charity : charities) {
@@ -96,8 +139,7 @@ INSERT INTO charities (org_number, name, status, url, is_pre_approved)
}
temp_update.executeBatch();
- // Removes records from main charities table, that don't exist in the temp table.
- // This makes it so if any organization is removed by IK, it will also be removed from the database.
+ // Removes records not found in the list from charities table
String sql_delete_old_records = """
DELETE FROM charities c
WHERE NOT EXISTS (
@@ -118,7 +160,6 @@ WHERE NOT EXISTS (
} catch (SQLException e) {
- // prints whole sql error message
e.printStackTrace();
throw new RuntimeException("ERROR: Something went wrong during updating charities table.");
}
From 273d9a51bc8093d86b8adc3179dbf8fd4a4da8de Mon Sep 17 00:00:00 2001
From: Roar
Date: Thu, 5 Mar 2026 00:35:01 +0100
Subject: [PATCH 26/77] Updated DatabaseManager
Made sql statements into transaction statements for increased stability. Converted "temp" table into a fully temporary table. Reused a single String variable for sql queries instead of one variable per statement.
---
.../team6/models/DatabaseManager.java | 93 +++++++++++--------
1 file changed, 54 insertions(+), 39 deletions(-)
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/DatabaseManager.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/DatabaseManager.java
index a4e2ce8..eacb4e7 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/DatabaseManager.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/DatabaseManager.java
@@ -59,7 +59,7 @@ public DatabaseManager() {
*/
public void createCharitiesTable() {
- String sql_create_main_table = """
+ String sql_query = """
CREATE TABLE IF NOT EXISTS charities (
org_number VARCHAR(100) PRIMARY KEY,
name VARCHAR(255),
@@ -71,7 +71,7 @@ url VARCHAR(255),
try (Connection conn = DriverManager.getConnection(databaseURL, username, password);
Statement s = conn.createStatement()) {
- s.execute(sql_create_main_table);
+ s.execute(sql_query);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException("Error creating tables.");
@@ -95,11 +95,12 @@ url VARCHAR(255),
*/
public void updateCharities(List charities) {
-
- try (Connection conn = DriverManager.getConnection(databaseURL, username, password))
- {
+ Connection conn = null;
+ try {
+ conn = DriverManager.getConnection(databaseURL, username, password);
+ conn.setAutoCommit(false);
// Inserts objects values into charities
- String sql_update = """
+ String sql_query = """
INSERT INTO charities (org_number, name, status, url, is_pre_approved)
VALUES (?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
@@ -109,38 +110,39 @@ INSERT INTO charities (org_number, name, status, url, is_pre_approved)
is_pre_approved = VALUES(is_pre_approved)
""";
- PreparedStatement s = conn.prepareStatement(sql_update);
-
- for (APICharityData charity : charities) {
- s.setString(1, charity.getOrg_number().replaceAll("\\s", ""));
- s.setString(2, charity.getName());
- s.setString(3, charity.getStatus());
- s.setString(4, charity.getUrl());
- s.setBoolean(5, charity.getIs_pre_approved());
-
- s.addBatch();
+ try (PreparedStatement s = conn.prepareStatement(sql_query)) {
+ for (APICharityData charity : charities) {
+ s.setString(1, charity.getOrg_number().replaceAll("\\s", ""));
+ s.setString(2, charity.getName());
+ s.setString(3, charity.getStatus());
+ s.setString(4, charity.getUrl());
+ s.setBoolean(5, charity.getIs_pre_approved());
+
+ s.addBatch();
+ }
+ s.executeBatch();
}
- s.executeBatch();
- // Temporary table for parity check
- String sql_drop_temp_table = "DROP TABLE IF EXISTS temp";
- PreparedStatement drop_temp_table = conn.prepareStatement(sql_drop_temp_table);
- drop_temp_table.execute();
- String sql_create_temp_table = "CREATE TABLE temp (org_number VARCHAR(100) PRIMARY KEY)";
- PreparedStatement temp_create = conn.prepareStatement(sql_create_temp_table);
- temp_create.execute();
+ // Temporary table for parity check
+ sql_query = "CREATE TEMPORARY TABLE temp (org_number VARCHAR(100) PRIMARY KEY)";
+ try (PreparedStatement temp_create = conn.prepareStatement(sql_query)) {
+ temp_create.execute();
+ }
String sql_update_temp_table = "INSERT INTO temp (org_number) VALUES (?)";
- PreparedStatement temp_update = conn.prepareStatement(sql_update_temp_table);
- for (APICharityData charity : charities) {
- temp_update.setString(1, charity.getOrg_number().replaceAll("\\s", ""));
- temp_update.addBatch();
- }
- temp_update.executeBatch();
+ try (PreparedStatement temp_update = conn.prepareStatement(sql_update_temp_table)) {
+ for (APICharityData charity : charities) {
+ temp_update.setString(1, charity.getOrg_number().replaceAll("\\s", ""));
+ temp_update.addBatch();
+ }
+ temp_update.executeBatch();
+ }
+
+
// Removes records not found in the list from charities table
- String sql_delete_old_records = """
+ sql_query = """
DELETE FROM charities c
WHERE NOT EXISTS (
SELECT 1
@@ -148,20 +150,33 @@ WHERE NOT EXISTS (
WHERE c.org_number = t.org_number
)
""";
- PreparedStatement delete_old_records = conn.prepareStatement(sql_delete_old_records);
- delete_old_records.execute();
-
- // Deletes temporary table
- String sql_delete_temp_table = "DROP TABLE IF EXISTS temp";
- PreparedStatement delete_temp_table = conn.prepareStatement(sql_delete_temp_table);
- delete_temp_table.execute();
+ try (PreparedStatement delete_old_records = conn.prepareStatement(sql_query)){
+ delete_old_records.execute();
+ }
+ conn.commit();
+ } catch (SQLException e) {
+ if (conn != null) {
+ try {
+ conn.rollback();
+ } catch (SQLException ex) {
+ ex.printStackTrace();
+ }
+ }
- } catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException("ERROR: Something went wrong during updating charities table.");
+ } finally {
+ if (conn != null) {
+ try {
+ conn.setAutoCommit(true);
+ conn.close();
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ }
}
}
From 3a453b34a54dd03eea95d77eaa7051ed37523451 Mon Sep 17 00:00:00 2001
From: Roar
Date: Thu, 5 Mar 2026 09:21:08 +0100
Subject: [PATCH 27/77] Updated pom.xml
Added H2 as a dependency, an in-memory mock database, useful for testing DatabaseManager logic.
---
helpmehelpapplication/pom.xml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/helpmehelpapplication/pom.xml b/helpmehelpapplication/pom.xml
index e921a97..ffac257 100644
--- a/helpmehelpapplication/pom.xml
+++ b/helpmehelpapplication/pom.xml
@@ -51,6 +51,12 @@
mysql-connector-j
9.6.0
+
+ com.h2database
+ h2
+ 2.4.240
+ test
+
From dce997e20cac360d4c5b8d199f486b1afbd828d2 Mon Sep 17 00:00:00 2001
From: Roar
Date: Thu, 5 Mar 2026 09:28:50 +0100
Subject: [PATCH 28/77] Updated DatabaseManager
Added a second constructor to make JUnit testing of the class feasible. Second constructor should not be used to create object in production code.
---
.../team6/models/DatabaseManager.java | 36 +++++++++++++++++--
1 file changed, 34 insertions(+), 2 deletions(-)
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/DatabaseManager.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/DatabaseManager.java
index eacb4e7..3d80458 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/DatabaseManager.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/DatabaseManager.java
@@ -50,6 +50,38 @@ public DatabaseManager() {
}
}
+ /**
+ * Constructs a new {@code DatabaseManager} using database credentials
+ *
+ * Used primarily for JUnit tests. Production should use the constructor
+ * using environment variables.
+ *
+ *
+ * @param databaseURL the url to the database
+ * @param username the username used to log in to the database
+ * @param password the password used to log in to the database
+ * @throws IllegalArgumentException if databaseURL, username, or password is
+ * {@code null} or blank
+ */
+
+ public DatabaseManager(String databaseURL, String username, String password) {
+ this.databaseURL = databaseURL;
+ this.username = username;
+ this.password = password;
+
+ if (this.databaseURL == null || this.databaseURL.isBlank()) {
+ throw new IllegalArgumentException("Database environment variable URL has not been set");
+ }
+
+ if (this.username == null || this.username.isBlank()) {
+ throw new IllegalArgumentException("Username environment variable has not been set");
+ }
+
+ if (this.password == null || this.password.isBlank()) {
+ throw new IllegalArgumentException("Password environment variable has not been set");
+ }
+ }
+
/**
* Creates the {@code charities} table if it does not already exist.
*
@@ -74,7 +106,7 @@ url VARCHAR(255),
s.execute(sql_query);
} catch (SQLException e) {
e.printStackTrace();
- throw new RuntimeException("Error creating tables.");
+ throw new RuntimeException("Error creating table.");
}
}
@@ -125,7 +157,7 @@ INSERT INTO charities (org_number, name, status, url, is_pre_approved)
// Temporary table for parity check
- sql_query = "CREATE TEMPORARY TABLE temp (org_number VARCHAR(100) PRIMARY KEY)";
+ sql_query = "CREATE TEMPORARY TABLE IF NOT EXISTS temp (org_number VARCHAR(100) PRIMARY KEY)";
try (PreparedStatement temp_create = conn.prepareStatement(sql_query)) {
temp_create.execute();
}
From cf33cfa8958ddfd8cf4dbae4640b449c108b83cb Mon Sep 17 00:00:00 2001
From: Roar
Date: Thu, 5 Mar 2026 10:37:27 +0100
Subject: [PATCH 29/77] Added DatabaseManagerTest
Added some coverage tests to check if it functions as intended. Does not currently have 100% coverage.
---
.../team6/models/DatabaseManagerTest.java | 235 ++++++++++++++++++
1 file changed, 235 insertions(+)
create mode 100644 helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/DatabaseManagerTest.java
diff --git a/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/DatabaseManagerTest.java b/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/DatabaseManagerTest.java
new file mode 100644
index 0000000..c73a57b
--- /dev/null
+++ b/helpmehelpapplication/src/test/java/ntnu/sytemutvikling/team6/models/DatabaseManagerTest.java
@@ -0,0 +1,235 @@
+package ntnu.sytemutvikling.team6.models;
+
+import org.junit.jupiter.api.*;
+import java.sql.*;
+import java.util.ArrayList;
+import java.util.List;
+import org.h2.jdbc.JdbcSQLNonTransientException;
+import static org.junit.jupiter.api.Assertions.*;
+
+class DatabaseManagerTest {
+
+ private DatabaseManager dbManager;
+ private String db_url;
+ private String username;
+ private String password;
+
+ @BeforeEach
+ void setUp() throws SQLException{
+ db_url = "jdbc:h2:mem:testdb;MODE=MySQL;DB_CLOSE_DELAY=-1";
+ username = "user";
+ password = "123";
+ dbManager = new DatabaseManager(db_url, username, password);
+
+ // Removes tables due to quirk with H2 that keeps temporary tables
+ try (Connection conn = DriverManager.getConnection(db_url, username, password)) {
+ PreparedStatement endstmt = conn.prepareStatement("DROP TABLE IF EXISTS charities, temp");
+ endstmt.execute();
+ }
+ }
+
+ @Test
+ void constructorShouldThrowIllegalArgumentExceptionIfDatabaseURLIsNull() {
+ assertThrows(IllegalArgumentException.class, () -> new DatabaseManager(null, username, password),
+ "DatabaseURL should not be allowed to be null.");
+ }
+
+ @Test
+ void constructorShouldThrowIllegalArgumentExceptionIfDatabaseURLIsBlankOrEmpty() {
+ assertThrows(IllegalArgumentException.class, () -> new DatabaseManager(" ", username, password),
+ "DatabaseURL should not be allowed to be blank.");
+ assertThrows(IllegalArgumentException.class, () -> new DatabaseManager("", username, password),
+ "DatabaseURL should not be allowed to be empty.");
+ }
+
+ @Test
+ void constructorShouldThrowIllegalArgumentExceptionIfUsernameIsNull() {
+ assertThrows(IllegalArgumentException.class, () -> new DatabaseManager(db_url, null, password),
+ "Database username should not be allowed to be null.");
+ }
+
+ @Test
+ void constructorShouldThrowIllegalArgumentExceptionIfUsernameIsBlankOrEmpty() {
+ assertThrows(IllegalArgumentException.class, () -> new DatabaseManager(db_url, " ", password),
+ "Username should not be allowed to be blank.");
+ assertThrows(IllegalArgumentException.class, () -> new DatabaseManager(db_url, "", password),
+ "Username should not be allowed to be empty.");
+ }
+
+ @Test
+ void constructorShouldThrowIllegalArgumentExceptionIfPasswordIsNull() {
+ assertThrows(IllegalArgumentException.class, () -> new DatabaseManager(db_url, username, null),
+ "Database password should not be allowed to be null.");
+ }
+
+ @Test
+ void constructorShouldThrowIllegalArgumentExceptionIfPasswordIsBlankOrEmpty() {
+ assertThrows(IllegalArgumentException.class, () -> new DatabaseManager(db_url, username, " "),
+ "Password should not be allowed to be blank.");
+ assertThrows(IllegalArgumentException.class, () -> new DatabaseManager(db_url, username, ""),
+ "Password should not be allowed to be empty.");
+ }
+
+ @Test
+ void createCharitiesTableShouldCreateTableSuccessfully() throws SQLException {
+ dbManager.createCharitiesTable();
+
+ try (Connection conn = DriverManager.getConnection(db_url, username, password)) {
+ ResultSet rs = conn.getMetaData().getTables(null, null,
+ "CHARITIES", null);
+
+ assertTrue(rs.next(), "Connection to the database should be successful.");
+ }
+ }
+
+ @Test
+ void updateCharitiesShouldInsertCorrectData() throws SQLException {
+ String org_number = "12345";
+ String name = "Svindelorg";
+ String status = "approved";
+ String url = "https://www.svindel.no";
+ boolean is_pre_approved = false;
+
+ var charity = new APICharityData(
+ org_number,
+ name,
+ status,
+ url,
+ is_pre_approved
+ );
+
+ dbManager.createCharitiesTable();
+ dbManager.updateCharities(List.of(charity));
+
+ try (Connection conn = DriverManager.getConnection(db_url, username, password);
+ PreparedStatement stmt = conn.prepareStatement("SELECT * FROM charities WHERE org_number = ?")) {
+
+ stmt.setString(1, org_number);
+ ResultSet rs = stmt.executeQuery();
+
+ assertTrue(rs.next());
+ assertEquals(org_number, rs.getString("org_number"), "Organization number should " +
+ "be correct.");
+ assertEquals(name, rs.getString("name"), "Name of the organization should be correct.");
+ assertEquals(status, rs.getString("status"), "Status of the organization should be " +
+ "correct.");
+ assertEquals(url, rs.getString("url"), "Url of the organization should be correct.");
+ assertEquals(is_pre_approved, rs.getBoolean("is_pre_approved"), "Is_pre_approved " +
+ "value should be correct.");
+ }
+ }
+
+ @Test
+ void updateCharitiesShouldRemoveDataNotInList() throws SQLException {
+ String org_number = "12345";
+ String name = "Svindelorg";
+ String status = "approved";
+ String url = "https://www.svindel.no";
+ boolean is_pre_approved = false;
+
+ var charity1 = new APICharityData(
+ org_number,
+ name,
+ status,
+ url,
+ is_pre_approved
+ );
+
+ org_number = "23456";
+ name = "SvindelKoin";
+ status = "approved";
+ url = "https://www.svindel.net";
+ is_pre_approved = true;
+
+ var charity2 = new APICharityData(
+ org_number,
+ name,
+ status,
+ url,
+ is_pre_approved
+ );
+
+ org_number = "345672";
+ name = "Arme Svindlere";
+ status = "approved";
+ url = "https://www.armesvindlere.com";
+ is_pre_approved = false;
+
+ var charity3 = new APICharityData(
+ org_number,
+ name,
+ status,
+ url,
+ is_pre_approved
+ );
+
+ var charityListBefore = new ArrayList();
+ charityListBefore.add(charity1);
+ charityListBefore.add(charity2);
+ charityListBefore.add(charity3);
+
+ var charityListAfter = new ArrayList();
+ charityListAfter.add(charity1);
+ charityListAfter.add(charity3);
+
+ dbManager.createCharitiesTable();
+ dbManager.updateCharities(charityListBefore);
+
+ try (Connection conn = DriverManager.getConnection(db_url, username, password)) {
+ PreparedStatement stmt = conn.prepareStatement("SELECT COUNT(org_number) AS number_b FROM charities");
+
+ ResultSet rs = stmt.executeQuery();
+
+ assertTrue(rs.next(), "Charities count row should exist.");
+ assertEquals(3, rs.getInt("number_b"), "The amount of org_numbers in the table" +
+ "should be 3.");
+ PreparedStatement endstmt = conn.prepareStatement("DROP TABLE IF EXISTS temp");
+ endstmt.execute();
+ }
+
+ dbManager.updateCharities(charityListAfter);
+
+ try (Connection conn = DriverManager.getConnection(db_url, username, password)) {
+ PreparedStatement stmt = conn.prepareStatement("SELECT COUNT(org_number) AS number_a FROM charities");
+
+ ResultSet rs = stmt.executeQuery();
+
+ assertTrue(rs.next(), "Charities count row should exist.");
+ assertEquals(2, rs.getInt("number_a"), "The amount of org_numbers in the table" +
+ "should be 2 due to removal of 1 table.");
+ }
+ }
+
+ @Test
+ void tempTableShouldNotExistAfterUpdating() throws SQLException{
+ try (Connection conn = DriverManager.getConnection(db_url, username, password)) {
+ PreparedStatement endstmt = conn.prepareStatement("DROP TABLE IF EXISTS charities, temp");
+ endstmt.execute();
+ }
+ String org_number = "12345";
+ String name = "Svindelorg";
+ String status = "approved";
+ String url = "https://www.svindel.no";
+ boolean is_pre_approved = false;
+
+ var charity = new APICharityData(
+ org_number,
+ name,
+ status,
+ url,
+ is_pre_approved
+ );
+
+ dbManager.createCharitiesTable();
+ dbManager.updateCharities(List.of(charity));
+
+ try (Connection conn = DriverManager.getConnection(db_url, username, password)) {
+ PreparedStatement stmt = conn.prepareStatement("SELECT * FROM temp");
+
+ ResultSet rs = stmt.executeQuery();
+
+ assertThrows(JdbcSQLNonTransientException.class, () -> rs.getString("org_number"),
+ "Exception about table being empty should be thrown.");
+ }
+ }
+}
\ No newline at end of file
From cf0dde899584afbaaf34491e330a9f81302be0e9 Mon Sep 17 00:00:00 2001
From: Roar
Date: Thu, 5 Mar 2026 10:38:26 +0100
Subject: [PATCH 30/77] Updated DatabaseManager
Removed IF NOT EXISTS for temporary table as it gave unintended results in unit tests.
---
.../java/ntnu/sytemutvikling/team6/models/DatabaseManager.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/DatabaseManager.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/DatabaseManager.java
index 3d80458..eb1530b 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/DatabaseManager.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/DatabaseManager.java
@@ -157,7 +157,7 @@ INSERT INTO charities (org_number, name, status, url, is_pre_approved)
// Temporary table for parity check
- sql_query = "CREATE TEMPORARY TABLE IF NOT EXISTS temp (org_number VARCHAR(100) PRIMARY KEY)";
+ sql_query = "CREATE TEMPORARY TABLE temp (org_number VARCHAR(100) PRIMARY KEY)";
try (PreparedStatement temp_create = conn.prepareStatement(sql_query)) {
temp_create.execute();
}
From 04558b2c24457c0956b61c16ede12f159b5353ec Mon Sep 17 00:00:00 2001
From: AdrianBalunan
Date: Thu, 5 Mar 2026 13:13:55 +0100
Subject: [PATCH 31/77] Feat: Gitignore target directory
---
.gitignore | 1 +
1 file changed, 1 insertion(+)
diff --git a/.gitignore b/.gitignore
index f28f5be..660d9b6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
# Adrian
.vscode/
.idea/
+helpmehelpapplication/target
From 22f5f24d83c8236a1fd76df8b4d75aa72e670783 Mon Sep 17 00:00:00 2001
From: AdrianBalunan
Date: Thu, 5 Mar 2026 14:09:34 +0100
Subject: [PATCH 32/77] Feat: Dedicated Directory
---
.../team6/models/{ => API}/APICharityData.java | 2 +-
.../team6/models/{ => API}/APICharityScraper.java | 2 +-
.../team6/models/{ => API}/DatabaseManager.java | 6 +++---
.../team6/models/{ => API}/IKOrganizationScraper.java | 3 ++-
4 files changed, 7 insertions(+), 6 deletions(-)
rename helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/{ => API}/APICharityData.java (98%)
rename helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/{ => API}/APICharityScraper.java (98%)
rename helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/{ => API}/DatabaseManager.java (99%)
rename helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/{ => API}/IKOrganizationScraper.java (97%)
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityData.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/API/APICharityData.java
similarity index 98%
rename from helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityData.java
rename to helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/API/APICharityData.java
index 10f3acf..b58f60b 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityData.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/API/APICharityData.java
@@ -1,4 +1,4 @@
-package ntnu.sytemutvikling.team6.models;
+package ntnu.sytemutvikling.team6.models.API;
/**
* Represents data parsed from the IK API JSON response.
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityScraper.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/API/APICharityScraper.java
similarity index 98%
rename from helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityScraper.java
rename to helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/API/APICharityScraper.java
index e92baca..e674505 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/APICharityScraper.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/API/APICharityScraper.java
@@ -1,4 +1,4 @@
-package ntnu.sytemutvikling.team6.models;
+package ntnu.sytemutvikling.team6.models.API;
import com.google.gson.Gson;
import java.io.IOException;
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/DatabaseManager.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/API/DatabaseManager.java
similarity index 99%
rename from helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/DatabaseManager.java
rename to helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/API/DatabaseManager.java
index eb1530b..01b3223 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/DatabaseManager.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/API/DatabaseManager.java
@@ -1,4 +1,4 @@
-package ntnu.sytemutvikling.team6.models;
+package ntnu.sytemutvikling.team6.models.API;
import java.sql.*;
import java.util.List;
@@ -151,9 +151,9 @@ INSERT INTO charities (org_number, name, status, url, is_pre_approved)
s.setBoolean(5, charity.getIs_pre_approved());
s.addBatch();
- }
+ }
s.executeBatch();
- }
+ }
// Temporary table for parity check
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/IKOrganizationScraper.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/API/IKOrganizationScraper.java
similarity index 97%
rename from helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/IKOrganizationScraper.java
rename to helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/API/IKOrganizationScraper.java
index e43eef0..16dcc06 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/IKOrganizationScraper.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/API/IKOrganizationScraper.java
@@ -1,4 +1,4 @@
-package ntnu.sytemutvikling.team6.models;
+package ntnu.sytemutvikling.team6.models.API;
import java.io.File;
import java.io.FileWriter;
@@ -8,6 +8,7 @@
import java.util.Collections;
import java.util.List;
import com.opencsv.CSVWriter;
+import ntnu.sytemutvikling.team6.models.Organization;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
From 4d0daae824b896cb7e8cb4a9b06d8ee7f3d0226a Mon Sep 17 00:00:00 2001
From: Robin Strand Prestmo
Date: Tue, 10 Mar 2026 20:47:27 +0100
Subject: [PATCH 33/77] Update README.md
Added Robin's full name
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index c2b4d1f..8ee4bba 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@ Consists of: Cathrine Kristiansen, Adrian Balunan, Roar Fagerkind, Robin Prestmo
- Team number: `6`
- Team members:
- - Robin Prestmo - Product Owner
+ - Robin Strand Prestmo - Product Owner
- Adrian Balunan - Scrum Master
- Cathrine Kristiansen - Archive and Document Responsible
- Roar Fagerkind - Software Quality Responsible
From 324a2c8a22bef1ddc5e8b460c936759cc068c879 Mon Sep 17 00:00:00 2001
From: Robin Strand Prestmo
Date: Tue, 10 Mar 2026 20:49:35 +0100
Subject: [PATCH 34/77] Update ci.yml
Changed runs-on: til [self-hosted, macOS, ARM64]
---
.github/workflows/ci.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index eac943d..68dd315 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -8,7 +8,7 @@ on:
jobs:
test:
- runs-on: self-hosted
+ runs-on: [self-hosted, macOS, ARM64]
steps:
- uses: actions/checkout@v4
From 3062e04f9ff6290dcfb6715ae956b2e934f33a59 Mon Sep 17 00:00:00 2001
From: Robin Strand Prestmo
Date: Tue, 10 Mar 2026 20:53:21 +0100
Subject: [PATCH 35/77] Update ci.yml
Added develop branch
---
.github/workflows/ci.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 68dd315..19000d1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,9 +2,9 @@ name: ci
on:
push:
- branches: [main]
+ branches: [main, develop]
pull_request:
- branches: [main]
+ branches: [main, develop]
jobs:
test:
From 8522115e16851c1b140a30df2f0278810977893c Mon Sep 17 00:00:00 2001
From: Robin Strand Prestmo
Date: Tue, 10 Mar 2026 20:59:58 +0100
Subject: [PATCH 36/77] Update ci.yml
Runs-on: self-hosted
---
.github/workflows/ci.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 19000d1..a4d5bd8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -8,7 +8,7 @@ on:
jobs:
test:
- runs-on: [self-hosted, macOS, ARM64]
+ runs-on: self-hosted
steps:
- uses: actions/checkout@v4
From e49504186307561aa5b607b0a9b3f4920bb6eb59 Mon Sep 17 00:00:00 2001
From: AdrianBalunan
Date: Wed, 11 Mar 2026 11:38:18 +0100
Subject: [PATCH 37/77] Feat: Moved API-folder seperatly from models folder
---
.../sytemutvikling/team6/{models => }/API/APICharityData.java | 2 +-
.../team6/{models => }/API/APICharityScraper.java | 2 +-
.../sytemutvikling/team6/{models => }/API/DatabaseManager.java | 2 +-
.../team6/{models => }/API/IKOrganizationScraper.java | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
rename helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/{models => }/API/APICharityData.java (98%)
rename helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/{models => }/API/APICharityScraper.java (98%)
rename helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/{models => }/API/DatabaseManager.java (99%)
rename helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/{models => }/API/IKOrganizationScraper.java (98%)
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/API/APICharityData.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/APICharityData.java
similarity index 98%
rename from helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/API/APICharityData.java
rename to helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/APICharityData.java
index b58f60b..b8ff21b 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/API/APICharityData.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/APICharityData.java
@@ -1,4 +1,4 @@
-package ntnu.sytemutvikling.team6.models.API;
+package ntnu.sytemutvikling.team6.API;
/**
* Represents data parsed from the IK API JSON response.
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/API/APICharityScraper.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/APICharityScraper.java
similarity index 98%
rename from helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/API/APICharityScraper.java
rename to helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/APICharityScraper.java
index e674505..1668b70 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/API/APICharityScraper.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/APICharityScraper.java
@@ -1,4 +1,4 @@
-package ntnu.sytemutvikling.team6.models.API;
+package ntnu.sytemutvikling.team6.API;
import com.google.gson.Gson;
import java.io.IOException;
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/API/DatabaseManager.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/DatabaseManager.java
similarity index 99%
rename from helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/API/DatabaseManager.java
rename to helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/DatabaseManager.java
index 01b3223..7afd17a 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/API/DatabaseManager.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/DatabaseManager.java
@@ -1,4 +1,4 @@
-package ntnu.sytemutvikling.team6.models.API;
+package ntnu.sytemutvikling.team6.API;
import java.sql.*;
import java.util.List;
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/API/IKOrganizationScraper.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/IKOrganizationScraper.java
similarity index 98%
rename from helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/API/IKOrganizationScraper.java
rename to helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/IKOrganizationScraper.java
index 16dcc06..0efd70e 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/API/IKOrganizationScraper.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/IKOrganizationScraper.java
@@ -1,4 +1,4 @@
-package ntnu.sytemutvikling.team6.models.API;
+package ntnu.sytemutvikling.team6.API;
import java.io.File;
import java.io.FileWriter;
From d9173e656f582b4abfaca33e0cc91cc251a1b505 Mon Sep 17 00:00:00 2001
From: AdrianBalunan
Date: Wed, 11 Mar 2026 12:14:37 +0100
Subject: [PATCH 38/77] Fix: Updated MySQL createTable Manager to match and
implement minaimal viable functions
---
.../team6/API/DatabaseManager.java | 60 ++++++++++++++++---
1 file changed, 53 insertions(+), 7 deletions(-)
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/DatabaseManager.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/DatabaseManager.java
index 7afd17a..6a8f14d 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/DatabaseManager.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/DatabaseManager.java
@@ -92,13 +92,59 @@ public DatabaseManager(String databaseURL, String username, String password) {
public void createCharitiesTable() {
String sql_query = """
- CREATE TABLE IF NOT EXISTS charities (
- org_number VARCHAR(100) PRIMARY KEY,
- name VARCHAR(255),
- status VARCHAR(50),
- url VARCHAR(255),
- is_pre_approved BOOLEAN
- )""";
+ -- MySQL Workbench Forward Engineering
+
+ SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
+ SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
+ SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';
+
+ -- -----------------------------------------------------
+ -- Schema HelpMeHelp
+ -- -----------------------------------------------------
+
+ -- -----------------------------------------------------
+ -- Schema HelpMeHelp
+ -- -----------------------------------------------------
+ --CREATE SCHEMA IF NOT EXISTS `HelpMeHelp` DEFAULT CHARACTER SET utf8 ;
+ --USE `HelpMeHelp` ;
+ USE apbaluna;
+
+ -- -----------------------------------------------------
+ -- Table `HelpMeHelp`.`Charities`
+ -- -----------------------------------------------------
+ CREATE TABLE IF NOT EXISTS `HelpMeHelp`.`Charities` (
+ `UUID` CHAR(36) NOT NULL,
+ `charity_name` VARCHAR(255) NOT NULL,
+ `charity_description` VARCHAR(255) NOT NULL,
+ `isVerified` TINYINT NOT NULL,
+ `category` VARCHAR(45) NOT NULL,
+ PRIMARY KEY (`UUID`))
+ ENGINE = InnoDB;
+
+
+ -- -----------------------------------------------------
+ -- Table `HelpMeHelp`.`Donations`
+ -- -----------------------------------------------------
+ CREATE TABLE IF NOT EXISTS `HelpMeHelp`.`Donations` (
+ `UUID` CHAR(36) NOT NULL,
+ `amount` DECIMAL NOT NULL,
+ `date` DATE NOT NULL,
+ `Charities_UUID` CHAR(36) NOT NULL,
+ PRIMARY KEY (`UUID`),
+ INDEX `fk_Donations_Charities_idx` (`Charities_UUID` ASC) VISIBLE,
+ CONSTRAINT `fk_Donations_Charities`
+ FOREIGN KEY (`Charities_UUID`)
+ REFERENCES `HelpMeHelp`.`Charities` (`UUID`)
+ ON DELETE NO ACTION
+ ON UPDATE NO ACTION)
+ ENGINE = InnoDB;
+
+
+ SET SQL_MODE=@OLD_SQL_MODE;
+ SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
+ SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;
+
+ """;
try (Connection conn = DriverManager.getConnection(databaseURL, username, password);
Statement s = conn.createStatement()) {
From 119137c2d230d1536245f3971e341f57603f4d05 Mon Sep 17 00:00:00 2001
From: AdrianBalunan
Date: Wed, 11 Mar 2026 14:26:33 +0100
Subject: [PATCH 39/77] Fix: Changed package locations for better structure
---
.../sytemutvikling/team6/{API => DAO}/APICharityData.java | 6 +++++-
.../team6/{API => database}/DatabaseManager.java | 4 +++-
.../team6/{API => scraper}/APICharityScraper.java | 4 +++-
.../team6/{API => scraper}/IKOrganizationScraper.java | 2 +-
4 files changed, 12 insertions(+), 4 deletions(-)
rename helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/{API => DAO}/APICharityData.java (92%)
rename helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/{API => database}/DatabaseManager.java (99%)
rename helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/{API => scraper}/APICharityScraper.java (97%)
rename helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/{API => scraper}/IKOrganizationScraper.java (99%)
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/APICharityData.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/DAO/APICharityData.java
similarity index 92%
rename from helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/APICharityData.java
rename to helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/DAO/APICharityData.java
index b8ff21b..5a50147 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/APICharityData.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/DAO/APICharityData.java
@@ -1,4 +1,8 @@
-package ntnu.sytemutvikling.team6.API;
+package ntnu.sytemutvikling.team6.DAO;
+
+import ntnu.sytemutvikling.team6.scraper.APICharityScraper;
+import ntnu.sytemutvikling.team6.database.DatabaseManager;
+import ntnu.sytemutvikling.team6.scraper.IKOrganizationScraper;
/**
* Represents data parsed from the IK API JSON response.
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/DatabaseManager.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/database/DatabaseManager.java
similarity index 99%
rename from helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/DatabaseManager.java
rename to helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/database/DatabaseManager.java
index 6a8f14d..4968159 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/DatabaseManager.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/database/DatabaseManager.java
@@ -1,4 +1,6 @@
-package ntnu.sytemutvikling.team6.API;
+package ntnu.sytemutvikling.team6.database;
+
+import ntnu.sytemutvikling.team6.DAO.APICharityData;
import java.sql.*;
import java.util.List;
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/APICharityScraper.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/scraper/APICharityScraper.java
similarity index 97%
rename from helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/APICharityScraper.java
rename to helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/scraper/APICharityScraper.java
index 1668b70..72319ab 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/APICharityScraper.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/scraper/APICharityScraper.java
@@ -1,6 +1,8 @@
-package ntnu.sytemutvikling.team6.API;
+package ntnu.sytemutvikling.team6.scraper;
import com.google.gson.Gson;
+import ntnu.sytemutvikling.team6.DAO.APICharityData;
+
import java.io.IOException;
import java.net.*;
import java.net.http.HttpClient;
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/IKOrganizationScraper.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/scraper/IKOrganizationScraper.java
similarity index 99%
rename from helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/IKOrganizationScraper.java
rename to helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/scraper/IKOrganizationScraper.java
index 0efd70e..672b7d6 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/API/IKOrganizationScraper.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/scraper/IKOrganizationScraper.java
@@ -1,4 +1,4 @@
-package ntnu.sytemutvikling.team6.API;
+package ntnu.sytemutvikling.team6.scraper;
import java.io.File;
import java.io.FileWriter;
From 0e9df82cd102ddef3fde5789efbdeea2e4cc14a2 Mon Sep 17 00:00:00 2001
From: AdrianBalunan
Date: Wed, 11 Mar 2026 15:09:07 +0100
Subject: [PATCH 40/77] Feat: APICharityData helps with GETTING data
---
.../sytemutvikling/team6/{DAO => scraper}/APICharityData.java | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
rename helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/{DAO => scraper}/APICharityData.java (94%)
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/DAO/APICharityData.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/scraper/APICharityData.java
similarity index 94%
rename from helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/DAO/APICharityData.java
rename to helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/scraper/APICharityData.java
index 5a50147..1a9bd2b 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/DAO/APICharityData.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/scraper/APICharityData.java
@@ -1,8 +1,6 @@
-package ntnu.sytemutvikling.team6.DAO;
+package ntnu.sytemutvikling.team6.scraper;
-import ntnu.sytemutvikling.team6.scraper.APICharityScraper;
import ntnu.sytemutvikling.team6.database.DatabaseManager;
-import ntnu.sytemutvikling.team6.scraper.IKOrganizationScraper;
/**
* Represents data parsed from the IK API JSON response.
From 6c509f05e89752457d24857b75ead17402065e86 Mon Sep 17 00:00:00 2001
From: AdrianBalunan
Date: Wed, 11 Mar 2026 15:09:55 +0100
Subject: [PATCH 41/77] Feat: APIScraper can will update the database
---
.../team6/scraper/APICharityScraper.java | 111 +++++++++++++++++-
1 file changed, 109 insertions(+), 2 deletions(-)
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/scraper/APICharityScraper.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/scraper/APICharityScraper.java
index 72319ab..db2482f 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/scraper/APICharityScraper.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/scraper/APICharityScraper.java
@@ -1,24 +1,29 @@
package ntnu.sytemutvikling.team6.scraper;
import com.google.gson.Gson;
-import ntnu.sytemutvikling.team6.DAO.APICharityData;
+import ntnu.sytemutvikling.team6.database.DatabaseConnection;
import java.io.IOException;
import java.net.*;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Fetches JSON information from the IK API and parses the JSON into a list of {@link APICharityData} objects.
+ * AND can update the database.
*/
public class APICharityScraper {
private final HttpClient client;
private final HttpRequest request;
+ private final DatabaseConnection connection;
/**
* Constructs a new APICharityScraper object.
@@ -32,7 +37,8 @@ public APICharityScraper(HttpClient client) throws URISyntaxException {
.uri(new URI("https://app.innsamlingskontrollen.no/api/public/v1/all"))
.GET()
.build();
- }
+ this.connection = new DatabaseConnection();
+ }
/**
* Checks if the http request returns an 'OK' response.
@@ -89,5 +95,106 @@ public List parseJSON(String JSONData) {
.filter(c-> !"obs".equalsIgnoreCase(c.getStatus()))
.toList();
}
+ /**
+ * Synchronizes the {@code charities} table with the provided list of APICharityData.
+ *
+ * Summary of function:
+ *
+ * - Inserts new records
+ * - Updates existing records using {@code ON DUPLICATE KEY UPDATE}
+ * - Removes database records that are not present in the provided list
+ *
+ * A temporary table is used to determine which records should be deleted.
+ *
+ *
+ * @param charities a list of ApiCharityDaya objects
+ * @throws RuntimeException if a {@link SQLException} occurs during database synchronization
+ */
+
+ public void updateCharities(List charities) {
+ Connection conn = null;
+ try {
+ conn = connection.getMySqlConnection();
+ conn.setAutoCommit(false);
+ // Inserts objects values into charities
+ String sql_query = """
+ INSERT INTO charities (org_number, name, status, url, is_pre_approved)
+ VALUES (?, ?, ?, ?, ?)
+ ON DUPLICATE KEY UPDATE
+ name = VALUES(name),
+ status = VALUES(status),
+ url = VALUES(url),
+ is_pre_approved = VALUES(is_pre_approved)
+ """;
+
+ try (PreparedStatement s = conn.prepareStatement(sql_query)) {
+ for (APICharityData charity : charities) {
+ s.setString(1, charity.getOrg_number().replaceAll("\\s", ""));
+ s.setString(2, charity.getName());
+ s.setString(3, charity.getStatus());
+ s.setString(4, charity.getUrl());
+ s.setBoolean(5, charity.getIs_pre_approved());
+
+ s.addBatch();
+ }
+ s.executeBatch();
+ }
+
+
+ // Temporary table for parity check
+ sql_query = "CREATE TEMPORARY TABLE temp (org_number VARCHAR(100) PRIMARY KEY)";
+ try (PreparedStatement temp_create = conn.prepareStatement(sql_query)) {
+ temp_create.execute();
+ }
+
+ String sql_update_temp_table = "INSERT INTO temp (org_number) VALUES (?)";
+ try (PreparedStatement temp_update = conn.prepareStatement(sql_update_temp_table)) {
+ for (APICharityData charity : charities) {
+ temp_update.setString(1, charity.getOrg_number().replaceAll("\\s", ""));
+ temp_update.addBatch();
+ }
+ temp_update.executeBatch();
+ }
+
+
+
+ // Removes records not found in the list from charities table
+ sql_query = """
+ DELETE FROM charities c
+ WHERE NOT EXISTS (
+ SELECT 1
+ FROM temp t
+ WHERE c.org_number = t.org_number
+ )
+ """;
+
+ try (PreparedStatement delete_old_records = conn.prepareStatement(sql_query)){
+ delete_old_records.execute();
+ }
+
+ conn.commit();
+ } catch (SQLException e) {
+
+ if (conn != null) {
+ try {
+ conn.rollback();
+ } catch (SQLException ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ e.printStackTrace();
+ throw new RuntimeException("ERROR: Something went wrong during updating charities table.");
+ } finally {
+ if (conn != null) {
+ try {
+ conn.setAutoCommit(true);
+ conn.close();
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
}
From 8bce5decff560a6e81fba9cce749f7aa515b3fcf Mon Sep 17 00:00:00 2001
From: AdrianBalunan
Date: Wed, 11 Mar 2026 15:10:17 +0100
Subject: [PATCH 42/77] Feat: Seperate File for Database Connection
---
.../team6/database/DatabaseConnection.java | 96 +++++++++++++++++++
1 file changed, 96 insertions(+)
create mode 100644 helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/database/DatabaseConnection.java
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/database/DatabaseConnection.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/database/DatabaseConnection.java
new file mode 100644
index 0000000..437bbf9
--- /dev/null
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/database/DatabaseConnection.java
@@ -0,0 +1,96 @@
+package ntnu.sytemutvikling.team6.database;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+/**
+ * Represents a reusable database connection through enviroment variables.
+ */
+
+public class DatabaseConnection {
+ private final String databaseURL;
+ private final String username;
+ private final String password;
+ /**
+ * Constructs a new {@code DatabaseConnection} using database credentials
+ * retrieved from system environment variables.
+ *
+ * Required environment variables:
+ *
+ * - {@code HMH_DB_URL}
+ * - {@code HMH_DB_USERNAME}
+ * - {@code HMH_DB_PASSWORD}
+ *
+ *
+ *
+ * @throws IllegalStateException if either databaseURL, username, or password is {@code null} or blank
+ */
+
+ // Values stored in system environment variables for security (using ntnu's phpmyadmin for this project)
+ public DatabaseConnection() {
+ this.databaseURL = System.getenv("HMH_DB_URL");
+ this.username = System.getenv("HMH_DB_USERNAME");
+ this.password = System.getenv("HMH_DB_PASSWORD");
+
+ if (this.databaseURL == null || this.databaseURL.isBlank()) {
+ throw new IllegalStateException("Database environment variable URL has not been set");
+ }
+
+ if (this.username == null || this.username.isBlank()) {
+ throw new IllegalStateException("Username environment variable has not been set");
+ }
+
+ if (this.password == null || this.password.isBlank()) {
+ throw new IllegalStateException("Password environment variable has not been set");
+ }
+ }
+
+ /**
+ * Constructs a new {@code DatabaseConnection} using database credentials
+ *
+ * Used primarily for JUnit tests. Production should use the constructor
+ * using environment variables.
+ *
+ *
+ * @param databaseURL the url to the database
+ * @param username the username used to log in to the database
+ * @param password the password used to log in to the database
+ * @throws IllegalArgumentException if databaseURL, username, or password is
+ * {@code null} or blank
+ */
+
+ public DatabaseConnection(String databaseURL, String username, String password) {
+ this.databaseURL = databaseURL;
+ this.username = username;
+ this.password = password;
+
+ if (this.databaseURL == null || this.databaseURL.isBlank()) {
+ throw new IllegalArgumentException("Database environment variable URL has not been set");
+ }
+
+ if (this.username == null || this.username.isBlank()) {
+ throw new IllegalArgumentException("Username environment variable has not been set");
+ }
+
+ if (this.password == null || this.password.isBlank()) {
+ throw new IllegalArgumentException("Password environment variable has not been set");
+ }
+ }
+
+
+ /**
+ * Creates and returns a new MySQL database connection.
+ *
+ * @return a {@link Connection} to the database
+ * @throws SQLException if the connection cannot be established
+ */
+ public Connection getMySqlConnection() throws SQLException {
+ return DriverManager.getConnection(databaseURL, username, password);
+ }
+
+ /**
+ *
+ */
+
+}
From 609ca49450a1511cab01a51ef978252716c564df Mon Sep 17 00:00:00 2001
From: AdrianBalunan
Date: Wed, 11 Mar 2026 15:10:43 +0100
Subject: [PATCH 43/77] Feat: Manager can only create tables if needed (for
now)
---
.../team6/database/DatabaseManager.java | 181 +-----------------
1 file changed, 10 insertions(+), 171 deletions(-)
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/database/DatabaseManager.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/database/DatabaseManager.java
index 4968159..3e5357d 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/database/DatabaseManager.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/database/DatabaseManager.java
@@ -1,87 +1,27 @@
package ntnu.sytemutvikling.team6.database;
-import ntnu.sytemutvikling.team6.DAO.APICharityData;
+import ntnu.sytemutvikling.team6.scraper.APICharityData;
import java.sql.*;
import java.util.List;
/**
- * Manages creation and synchronization of database tables in a MySQL database.
+ * Manages creation of MySQL tables (for now)
*
- * This class is responsible for establishing connections using environment variables
+ * This class is responsible for creating the tables needed for the application, if not done already
* and maintaining the {@code charities} table based on data retrieved from the IK API.
*
*/
public class DatabaseManager {
- private final String databaseURL;
- private final String username;
- private final String password;
+ private final DatabaseConnection connection;
/**
- Constructs a new {@code DatabaseManager} using database credentials
- * retrieved from system environment variables.
- *
- * Required environment variables:
- *
- * - {@code HMH_DB_URL}
- * - {@code HMH_DB_USERNAME}
- * - {@code HMH_DB_PASSWORD}
- *
- *
- *
- * @throws IllegalStateException if either databaseURL, username, or password is {@code null} or blank
- */
-
- // Values stored in system environment variables for security (using ntnu's phpmyadmin for this project)
- public DatabaseManager() {
- this.databaseURL = System.getenv("HMH_DB_URL");
- this.username = System.getenv("HMH_DB_USERNAME");
- this.password = System.getenv("HMH_DB_PASSWORD");
-
- if (this.databaseURL == null || this.databaseURL.isBlank()) {
- throw new IllegalStateException("Database environment variable URL has not been set");
- }
-
- if (this.username == null || this.username.isBlank()) {
- throw new IllegalStateException("Username environment variable has not been set");
- }
-
- if (this.password == null || this.password.isBlank()) {
- throw new IllegalStateException("Password environment variable has not been set");
- }
- }
-
- /**
- * Constructs a new {@code DatabaseManager} using database credentials
- *
- * Used primarily for JUnit tests. Production should use the constructor
- * using environment variables.
- *
- *
- * @param databaseURL the url to the database
- * @param username the username used to log in to the database
- * @param password the password used to log in to the database
- * @throws IllegalArgumentException if databaseURL, username, or password is
- * {@code null} or blank
+ * Contractor for DatabaseManager. It uses a DatabaseConnection object that contains a connection credentials
+ * using environment variables in the DatabaseConnection.
*/
-
- public DatabaseManager(String databaseURL, String username, String password) {
- this.databaseURL = databaseURL;
- this.username = username;
- this.password = password;
-
- if (this.databaseURL == null || this.databaseURL.isBlank()) {
- throw new IllegalArgumentException("Database environment variable URL has not been set");
- }
-
- if (this.username == null || this.username.isBlank()) {
- throw new IllegalArgumentException("Username environment variable has not been set");
- }
-
- if (this.password == null || this.password.isBlank()) {
- throw new IllegalArgumentException("Password environment variable has not been set");
- }
+ public DatabaseManager(){
+ this.connection = new DatabaseConnection();
}
/**
@@ -92,7 +32,7 @@ public DatabaseManager(String databaseURL, String username, String password) {
* @throws RuntimeException if a {@link SQLException} occurs while creating the table
*/
- public void createCharitiesTable() {
+ public void createTables() {
String sql_query = """
-- MySQL Workbench Forward Engineering
@@ -148,7 +88,7 @@ FOREIGN KEY (`Charities_UUID`)
""";
- try (Connection conn = DriverManager.getConnection(databaseURL, username, password);
+ try (Connection conn = connection.getMySqlConnection();
Statement s = conn.createStatement()) {
s.execute(sql_query);
@@ -158,106 +98,5 @@ FOREIGN KEY (`Charities_UUID`)
}
}
- /**
- * Synchronizes the {@code charities} table with the provided list of APICharityData.
- *
- * Summary of function:
- *
- * - Inserts new records
- * - Updates existing records using {@code ON DUPLICATE KEY UPDATE}
- * - Removes database records that are not present in the provided list
- *
- * A temporary table is used to determine which records should be deleted.
- *
- *
- * @param charities a list of ApiCharityDaya objects
- * @throws RuntimeException if a {@link SQLException} occurs during database synchronization
- */
-
- public void updateCharities(List charities) {
- Connection conn = null;
- try {
- conn = DriverManager.getConnection(databaseURL, username, password);
- conn.setAutoCommit(false);
- // Inserts objects values into charities
- String sql_query = """
- INSERT INTO charities (org_number, name, status, url, is_pre_approved)
- VALUES (?, ?, ?, ?, ?)
- ON DUPLICATE KEY UPDATE
- name = VALUES(name),
- status = VALUES(status),
- url = VALUES(url),
- is_pre_approved = VALUES(is_pre_approved)
- """;
-
- try (PreparedStatement s = conn.prepareStatement(sql_query)) {
- for (APICharityData charity : charities) {
- s.setString(1, charity.getOrg_number().replaceAll("\\s", ""));
- s.setString(2, charity.getName());
- s.setString(3, charity.getStatus());
- s.setString(4, charity.getUrl());
- s.setBoolean(5, charity.getIs_pre_approved());
-
- s.addBatch();
- }
- s.executeBatch();
- }
-
-
- // Temporary table for parity check
- sql_query = "CREATE TEMPORARY TABLE temp (org_number VARCHAR(100) PRIMARY KEY)";
- try (PreparedStatement temp_create = conn.prepareStatement(sql_query)) {
- temp_create.execute();
- }
-
- String sql_update_temp_table = "INSERT INTO temp (org_number) VALUES (?)";
- try (PreparedStatement temp_update = conn.prepareStatement(sql_update_temp_table)) {
- for (APICharityData charity : charities) {
- temp_update.setString(1, charity.getOrg_number().replaceAll("\\s", ""));
- temp_update.addBatch();
- }
- temp_update.executeBatch();
- }
-
-
- // Removes records not found in the list from charities table
- sql_query = """
- DELETE FROM charities c
- WHERE NOT EXISTS (
- SELECT 1
- FROM temp t
- WHERE c.org_number = t.org_number
- )
- """;
-
- try (PreparedStatement delete_old_records = conn.prepareStatement(sql_query)){
- delete_old_records.execute();
- }
-
- conn.commit();
- } catch (SQLException e) {
-
- if (conn != null) {
- try {
- conn.rollback();
- } catch (SQLException ex) {
- ex.printStackTrace();
- }
- }
-
- e.printStackTrace();
- throw new RuntimeException("ERROR: Something went wrong during updating charities table.");
- } finally {
- if (conn != null) {
- try {
- conn.setAutoCommit(true);
- conn.close();
- } catch (SQLException e) {
- e.printStackTrace();
- }
- }
- }
-
- }
}
\ No newline at end of file
From 3f55f66e65c36ad5655d2118995011c65587d823 Mon Sep 17 00:00:00 2001
From: AdrianBalunan
Date: Wed, 11 Mar 2026 15:11:22 +0100
Subject: [PATCH 44/77] Feat: DAO data acsess object(s) will help SETTING new
data to database
---
.../sytemutvikling/team6/DAO/CharityDAO.java | 273 ++++++++++++++++++
.../sytemutvikling/team6/DAO/DonationDAO.java | 4 +
2 files changed, 277 insertions(+)
create mode 100644 helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/DAO/CharityDAO.java
create mode 100644 helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/DAO/DonationDAO.java
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/DAO/CharityDAO.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/DAO/CharityDAO.java
new file mode 100644
index 0000000..810eca1
--- /dev/null
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/DAO/CharityDAO.java
@@ -0,0 +1,273 @@
+package ntnu.sytemutvikling.team6.DAO;
+
+import ntnu.sytemutvikling.team6.scraper.APICharityData;
+
+import java.sql.*;
+import java.util.List;
+
+/**
+ * Manages the charity
+ *
+ * This class is responsible for establishing connections using environment variables
+ * and maintaining the {@code charities} table based on data retrieved from the IK API.
+ *
+ */
+
+public class CharityDAO {
+
+import APICharityData;
+
+import java.sql.*;
+import java.util.List;
+
+
+
+ public class DatabaseManager {
+ private final String databaseURL;
+ private final String username;
+ private final String password;
+
+ /**
+ Constructs a new {@code DatabaseManager} using database credentials
+ * retrieved from system environment variables.
+ *
+ * Required environment variables:
+ *
+ * - {@code HMH_DB_URL}
+ * - {@code HMH_DB_USERNAME}
+ * - {@code HMH_DB_PASSWORD}
+ *
+ *
+ *
+ * @throws IllegalStateException if either databaseURL, username, or password is {@code null} or blank
+ */
+
+ // Values stored in system environment variables for security (using ntnu's phpmyadmin for this project)
+ public DatabaseManager() {
+ this.databaseURL = System.getenv("HMH_DB_URL");
+ this.username = System.getenv("HMH_DB_USERNAME");
+ this.password = System.getenv("HMH_DB_PASSWORD");
+
+ if (this.databaseURL == null || this.databaseURL.isBlank()) {
+ throw new IllegalStateException("Database environment variable URL has not been set");
+ }
+
+ if (this.username == null || this.username.isBlank()) {
+ throw new IllegalStateException("Username environment variable has not been set");
+ }
+
+ if (this.password == null || this.password.isBlank()) {
+ throw new IllegalStateException("Password environment variable has not been set");
+ }
+ }
+
+ /**
+ * Constructs a new {@code DatabaseManager} using database credentials
+ *
+ * Used primarily for JUnit tests. Production should use the constructor
+ * using environment variables.
+ *
+ *
+ * @param databaseURL the url to the database
+ * @param username the username used to log in to the database
+ * @param password the password used to log in to the database
+ * @throws IllegalArgumentException if databaseURL, username, or password is
+ * {@code null} or blank
+ */
+
+ public DatabaseManager(String databaseURL, String username, String password) {
+ this.databaseURL = databaseURL;
+ this.username = username;
+ this.password = password;
+
+ if (this.databaseURL == null || this.databaseURL.isBlank()) {
+ throw new IllegalArgumentException("Database environment variable URL has not been set");
+ }
+
+ if (this.username == null || this.username.isBlank()) {
+ throw new IllegalArgumentException("Username environment variable has not been set");
+ }
+
+ if (this.password == null || this.password.isBlank()) {
+ throw new IllegalArgumentException("Password environment variable has not been set");
+ }
+ }
+
+ /**
+ * Creates the {@code charities} table if it does not already exist.
+ *
+ * The table structure is based on fields from {@link APICharityData}.
+ *
+ * @throws RuntimeException if a {@link SQLException} occurs while creating the table
+ */
+
+ public void createTables() {
+ String sql_query = """
+ -- MySQL Workbench Forward Engineering
+
+ SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
+ SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
+ SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';
+
+ -- -----------------------------------------------------
+ -- Schema HelpMeHelp
+ -- -----------------------------------------------------
+
+ -- -----------------------------------------------------
+ -- Schema HelpMeHelp
+ -- -----------------------------------------------------
+ --CREATE SCHEMA IF NOT EXISTS `HelpMeHelp` DEFAULT CHARACTER SET utf8 ;
+ --USE `HelpMeHelp` ;
+ USE apbaluna;
+
+ -- -----------------------------------------------------
+ -- Table `HelpMeHelp`.`Charities`
+ -- -----------------------------------------------------
+ CREATE TABLE IF NOT EXISTS `HelpMeHelp`.`Charities` (
+ `UUID` CHAR(36) NOT NULL,
+ `charity_name` VARCHAR(255) NOT NULL,
+ `charity_description` VARCHAR(255) NOT NULL,
+ `isVerified` TINYINT NOT NULL,
+ `category` VARCHAR(45) NOT NULL,
+ PRIMARY KEY (`UUID`))
+ ENGINE = InnoDB;
+
+
+ -- -----------------------------------------------------
+ -- Table `HelpMeHelp`.`Donations`
+ -- -----------------------------------------------------
+ CREATE TABLE IF NOT EXISTS `HelpMeHelp`.`Donations` (
+ `UUID` CHAR(36) NOT NULL,
+ `amount` DECIMAL NOT NULL,
+ `date` DATE NOT NULL,
+ `Charities_UUID` CHAR(36) NOT NULL,
+ PRIMARY KEY (`UUID`),
+ INDEX `fk_Donations_Charities_idx` (`Charities_UUID` ASC) VISIBLE,
+ CONSTRAINT `fk_Donations_Charities`
+ FOREIGN KEY (`Charities_UUID`)
+ REFERENCES `HelpMeHelp`.`Charities` (`UUID`)
+ ON DELETE NO ACTION
+ ON UPDATE NO ACTION)
+ ENGINE = InnoDB;
+
+
+ SET SQL_MODE=@OLD_SQL_MODE;
+ SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
+ SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;
+
+ """;
+
+ try (Connection conn = DriverManager.getConnection(databaseURL, username, password);
+ Statement s = conn.createStatement()) {
+
+ s.execute(sql_query);
+ } catch (SQLException e) {
+ e.printStackTrace();
+ throw new RuntimeException("Error creating table.");
+ }
+ }
+
+ /**
+ * Synchronizes the {@code charities} table with the provided list of APICharityData.
+ *
+ * Summary of function:
+ *
+ * - Inserts new records
+ * - Updates existing records using {@code ON DUPLICATE KEY UPDATE}
+ * - Removes database records that are not present in the provided list
+ *
+ * A temporary table is used to determine which records should be deleted.
+ *
+ *
+ * @param charities a list of ApiCharityDaya objects
+ * @throws RuntimeException if a {@link SQLException} occurs during database synchronization
+ */
+
+ public void updateCharities(List charities) {
+ Connection conn = null;
+ try {
+ conn = DriverManager.getConnection(databaseURL, username, password);
+ conn.setAutoCommit(false);
+ // Inserts objects values into charities
+ String sql_query = """
+ INSERT INTO charities (org_number, name, status, url, is_pre_approved)
+ VALUES (?, ?, ?, ?, ?)
+ ON DUPLICATE KEY UPDATE
+ name = VALUES(name),
+ status = VALUES(status),
+ url = VALUES(url),
+ is_pre_approved = VALUES(is_pre_approved)
+ """;
+
+ try (PreparedStatement s = conn.prepareStatement(sql_query)) {
+ for (APICharityData charity : charities) {
+ s.setString(1, charity.getOrg_number().replaceAll("\\s", ""));
+ s.setString(2, charity.getName());
+ s.setString(3, charity.getStatus());
+ s.setString(4, charity.getUrl());
+ s.setBoolean(5, charity.getIs_pre_approved());
+
+ s.addBatch();
+ }
+ s.executeBatch();
+ }
+
+
+ // Temporary table for parity check
+ sql_query = "CREATE TEMPORARY TABLE temp (org_number VARCHAR(100) PRIMARY KEY)";
+ try (PreparedStatement temp_create = conn.prepareStatement(sql_query)) {
+ temp_create.execute();
+ }
+
+ String sql_update_temp_table = "INSERT INTO temp (org_number) VALUES (?)";
+ try (PreparedStatement temp_update = conn.prepareStatement(sql_update_temp_table)) {
+ for (APICharityData charity : charities) {
+ temp_update.setString(1, charity.getOrg_number().replaceAll("\\s", ""));
+ temp_update.addBatch();
+ }
+ temp_update.executeBatch();
+ }
+
+
+
+ // Removes records not found in the list from charities table
+ sql_query = """
+ DELETE FROM charities c
+ WHERE NOT EXISTS (
+ SELECT 1
+ FROM temp t
+ WHERE c.org_number = t.org_number
+ )
+ """;
+
+ try (PreparedStatement delete_old_records = conn.prepareStatement(sql_query)){
+ delete_old_records.execute();
+ }
+
+ conn.commit();
+ } catch (SQLException e) {
+
+ if (conn != null) {
+ try {
+ conn.rollback();
+ } catch (SQLException ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ e.printStackTrace();
+ throw new RuntimeException("ERROR: Something went wrong during updating charities table.");
+ } finally {
+ if (conn != null) {
+ try {
+ conn.setAutoCommit(true);
+ conn.close();
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ }
+ }
+}
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/DAO/DonationDAO.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/DAO/DonationDAO.java
new file mode 100644
index 0000000..b902276
--- /dev/null
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/DAO/DonationDAO.java
@@ -0,0 +1,4 @@
+package ntnu.sytemutvikling.team6.DAO;
+
+public class DonationDAO {
+}
From f4fc2ba6d78b6641a376b1a780d7bf8f84beeeb8 Mon Sep 17 00:00:00 2001
From: AdrianBalunan
Date: Wed, 11 Mar 2026 15:48:29 +0100
Subject: [PATCH 45/77] Fix/Feat: Charity class looks very similar to
APICharityData, will keep APICharityData though.
---
.../sytemutvikling/team6/models/Charity.java | 86 +++++++++++--------
1 file changed, 52 insertions(+), 34 deletions(-)
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Charity.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Charity.java
index 2c2cb3f..103fe81 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Charity.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Charity.java
@@ -8,11 +8,10 @@
import java.util.ArrayList;
import java.util.List;
-import java.util.UUID;
public class Charity {
/* UUID for uniquely identifying each charity */
- private UUID id;
+ private String org_number;
/* Name of the charity */
private String name;
@@ -20,11 +19,10 @@ public class Charity {
/* Description of the charity's mission and activities */
private String description;
- /* Total Donations received */
- private int totalDonations;
-
/* Is the charity verified? */
- private boolean isVerified;
+ private String status;
+
+ private boolean is_pre_approved;
/* Category for the charity */
private String category;
@@ -33,26 +31,61 @@ public class Charity {
private List feedbacks;
/**
- * Konstructor for creating a new charity. The ID is generated automatically using UUID. Total
- * donations are initialized to 0. The charity is unverified by default.
+ * Contructor for creating a new charity. The charity is unverified by default.
*
- * @param name
- * @param description
- * @param category
+ * @param org_number matches from innsamlingkontrollen
+ * @param name matches from innsamlingkontrollen
+ * @param is_pre_approved name matches from innsamlingkontrollen
+ * @param status name matches from innsamlingkontrollen
+ * @param description no coreleation to innsamlingskontrollen
+ * @param category no coreleation to innsamlingskontrollen
+ *
+
*/
- public Charity(String name, String description, String category) {
- this.id = UUID.randomUUID();
+ public Charity(String org_number, String name, String description, String category, boolean is_pre_approved, String status) {
+ this.org_number = org_number.replaceAll("\\s", "");
this.name = name;
this.description = description;
- this.totalDonations = 0;
- this.isVerified = false;
+ this.is_pre_approved = is_pre_approved;
+ this.status = status;
this.feedbacks = new ArrayList<>();
this.category = category;
}
+ /**
+ * Contructor for creating a new charity. Taylored to match data given from Api.
+ * Other attributes will just be initialized as empty
+ *
+ * @param org_number matches from innsamlingkontrollen
+ * @param name matches from innsamlingkontrollen
+ * @param is_pre_approved name matches from innsamlingkontrollen
+ * @param status name matches from innsamlingkontrollen
+ *
+
+ */
+ public Charity(String org_number, String name, String category, boolean is_pre_approved, String status) {
+ this.org_number = org_number.replaceAll("\\s", "");
+ this.name = name;
+ this.description = "";
+ this.is_pre_approved = is_pre_approved;
+ this.status = status;
+ this.feedbacks = new ArrayList<>();
+ this.category = "";
+ }
+
/** Getters for the charity's attributes. */
- public UUID getId() {
- return id;
+ public String getOrg_number() {
+ return org_number;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public boolean getPreApproved(){return is_pre_approved;}
+
+ public List getFeedbacks() {
+ return feedbacks;
}
public String getCategory() {
@@ -67,29 +100,14 @@ public String getDescription() {
return description;
}
- public int getTotalDonations() {
- return totalDonations;
- }
-
- public boolean isVerified() {
- return isVerified;
- }
-
/** Setter for verification status. This one sets the charity as verified. */
public void setVerified() {
- this.isVerified = true;
+ this.status = "Approved";
}
/** Setter for verification status. This one sets the charity as unverified. */
public void setUnverified() {
- this.isVerified = false;
+ this.status = "Veto";
}
- /**
- * Setter for total donations. This method is used to update the total donations when a new
- * donation is made.
- */
- public void setTotalDonations(int amount) {
- this.totalDonations += amount;
- }
}
From 74301088bbf52eccc9f023ce0ea031dd86fb02bf Mon Sep 17 00:00:00 2001
From: AdrianBalunan
Date: Wed, 11 Mar 2026 16:02:10 +0100
Subject: [PATCH 46/77] Fix: Unused Variable in Constructor
---
.../src/main/java/ntnu/sytemutvikling/team6/models/Charity.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Charity.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Charity.java
index 103fe81..ccd2ce6 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Charity.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/Charity.java
@@ -63,7 +63,7 @@ public Charity(String org_number, String name, String description, String catego
*
*/
- public Charity(String org_number, String name, String category, boolean is_pre_approved, String status) {
+ public Charity(String org_number, String name, boolean is_pre_approved, String status) {
this.org_number = org_number.replaceAll("\\s", "");
this.name = name;
this.description = "";
From 55f142305331b0e2da66a4580d946d82acd3a98a Mon Sep 17 00:00:00 2001
From: AdrianBalunan
Date: Wed, 11 Mar 2026 16:25:19 +0100
Subject: [PATCH 47/77] Fix: Fix search method since is by org_number instead
of uuid
---
.../ntnu/sytemutvikling/team6/models/CharityRegistry.java | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/CharityRegistry.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/CharityRegistry.java
index 2935c74..a4f2b94 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/CharityRegistry.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/CharityRegistry.java
@@ -13,11 +13,11 @@ public List getAllCharities() {
return Collections.unmodifiableList(charities);
}
- public Optional findCharityById(UUID charityId) {
- if (charityId == null) {
+ public Optional findCharityByOrgnumber(String org_number) {
+ if (org_number == null) {
throw new IllegalArgumentException("CharityId can not be null.");
}
- return charities.stream().filter(charity -> charityId.equals(charity.getId())).findFirst();
+ return charities.stream().filter(charity -> org_number.equals(charity.getOrg_number())).findFirst();
}
public void addCharity(Charity charity) {
From 7f022b0ed15fccc5ce1ba228f01f48fe5ce735ca Mon Sep 17 00:00:00 2001
From: AdrianBalunan
Date: Wed, 11 Mar 2026 16:26:13 +0100
Subject: [PATCH 48/77] Fix: Fix search method since is by org_number instead
of uuid
---
.../ntnu/sytemutvikling/team6/models/CharityRegistry.java | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/CharityRegistry.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/CharityRegistry.java
index a4f2b94..6b151fd 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/CharityRegistry.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/models/CharityRegistry.java
@@ -27,10 +27,10 @@ public void addCharity(Charity charity) {
charities.add(charity);
}
- public boolean removeCharity(UUID charityId) {
- if (charityId == null) {
+ public boolean removeCharity(String org_number) {
+ if (org_number == null) {
throw new IllegalArgumentException("CharityId can not be null.");
}
- return charities.removeIf(charity -> charityId.equals(charity.getId()));
+ return charities.removeIf(charity -> org_number.equals(charity.getOrg_number()));
}
}
From 2a37af85ef0dd026e9455368ccb8b697ddcd33f3 Mon Sep 17 00:00:00 2001
From: AdrianBalunan
Date: Wed, 11 Mar 2026 16:54:57 +0100
Subject: [PATCH 49/77] Feat: Added a connection check in DatabaseManager
---
.../team6/database/DatabaseManager.java | 25 +++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/database/DatabaseManager.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/database/DatabaseManager.java
index 3e5357d..9e1fd3c 100644
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/database/DatabaseManager.java
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/database/DatabaseManager.java
@@ -24,6 +24,31 @@ public DatabaseManager(){
this.connection = new DatabaseConnection();
}
+ /**
+ * Connection test for the Database.
+ * Does a simple SELECT SQL query and returns either true og and Exception if failed
+ *
+ * @return true if Sucsedd or SQLExepction if failed
+ */
+ public boolean testConnection() {
+ try (Connection conn = connection.getMySqlConnection();
+ Statement stmt = conn.createStatement()) {
+
+ ResultSet rs = stmt.executeQuery("SELECT 1");
+
+ if (rs.next()) {
+ System.out.println("Database connection verified.");
+ return true;
+ }
+
+ } catch (SQLException e) {
+ System.out.println("Database connection failed.");
+ e.printStackTrace();
+ }
+
+ return false;
+ }
+
/**
* Creates the {@code charities} table if it does not already exist.
*
From 743573a93273072ea273369c00e71d2df5a1de86 Mon Sep 17 00:00:00 2001
From: AdrianBalunan
Date: Wed, 11 Mar 2026 16:55:31 +0100
Subject: [PATCH 50/77] Feat: Renamed Main method and added a simple test to
getting API information
---
.../sytemutvikling/team6/HmHApplication.java | 33 +++++++++++++++++++
.../java/ntnu/sytemutvikling/team6/Main.java | 7 ----
2 files changed, 33 insertions(+), 7 deletions(-)
create mode 100644 helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/HmHApplication.java
delete mode 100644 helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/Main.java
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/HmHApplication.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/HmHApplication.java
new file mode 100644
index 0000000..678dce5
--- /dev/null
+++ b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/HmHApplication.java
@@ -0,0 +1,33 @@
+package ntnu.sytemutvikling.team6;
+
+import ntnu.sytemutvikling.team6.models.Charity;
+import ntnu.sytemutvikling.team6.models.CharityRegistry;
+import ntnu.sytemutvikling.team6.scraper.APICharityScraper;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.http.HttpClient;
+
+public class HmHApplication {
+ public static void main(String[] args) {
+ init();
+ System.out.println("Hello world!");
+
+ }
+ public static void init(){
+ try {
+ HttpClient https = HttpClient.newHttpClient();
+ APICharityScraper scraper = new APICharityScraper(https);
+
+ if (scraper.checkConnection()){
+ CharityRegistry charityRegistry = scraper.parseJSON(scraper.getJSONData());
+ for (Charity charity : charityRegistry.getAllCharities()){
+ System.out.println(charity.toString());
+ }
+ }
+ } catch (Exception e){
+ e.printStackTrace();
+ }
+
+ }
+}
diff --git a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/Main.java b/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/Main.java
deleted file mode 100644
index b30c2e3..0000000
--- a/helpmehelpapplication/src/main/java/ntnu/sytemutvikling/team6/Main.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package ntnu.sytemutvikling.team6;
-
-public class Main {
- public static void main(String[] args) {
- System.out.println("Hello world!");
- }
-}
From 1fdcfec27cec2335a2f5d5d8f2f4c9e79f73b1b6 Mon Sep 17 00:00:00 2001
From: AdrianBalunan
Date: Wed, 11 Mar 2026 16:56:50 +0100
Subject: [PATCH 51/77] Feat: Enviroment variables file for developement. Add
this file to your configuration if you're developing the database throught
java.
---
helpmehelpapplication/src/main/resources/database.env | 9 +++++++++
1 file changed, 9 insertions(+)
create mode 100644 helpmehelpapplication/src/main/resources/database.env
diff --git a/helpmehelpapplication/src/main/resources/database.env b/helpmehelpapplication/src/main/resources/database.env
new file mode 100644
index 0000000..3f511f0
--- /dev/null
+++ b/helpmehelpapplication/src/main/resources/database.env
@@ -0,0 +1,9 @@
+DB_CONNECTION=mysql;
+DB_HOST=127.0.0.1;
+DB_PORT=3306;
+DB_DATABASE=apbaluna;
+DB_USERNAME=apbaluna;
+DB_PASSWORD=GYntUFPG;
+HMH_DB_URL=jdbc:mysql://127.0.0.1:3306/apbaluna;
+HMH_DB_USERNAME=apbaluna;
+HMH_DB_PASSWORD=GYntUFPG;
\ No newline at end of file
From 544f4fbfb60a90c98735d0ef428626d267dee613 Mon Sep 17 00:00:00 2001
From: Cathrine Kristiansen
Date: Thu, 12 Mar 2026 09:34:08 +0100
Subject: [PATCH 52/77] Add files via upload
---
.../Thursdays Meeting 5.02.2026 (With LA).pdf" | Bin 0 -> 136126 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 "docs/M\303\270tedokumenter/Thursdays Meeting 5.02.2026 (With LA).pdf"
diff --git "a/docs/M\303\270tedokumenter/Thursdays Meeting 5.02.2026 (With LA).pdf" "b/docs/M\303\270tedokumenter/Thursdays Meeting 5.02.2026 (With LA).pdf"
new file mode 100644
index 0000000000000000000000000000000000000000..ae429422774c47750a0f6b7d397ccf2ca0c0807c
GIT binary patch
literal 136126
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