diff --git "a/docs/M\303\270tedokumenter/Internmeeting 2026.02.05.pdf" "b/docs/M\303\270tedokumenter/Internmeeting 2026.02.05.pdf" new file mode 100644 index 0000000..e921157 Binary files /dev/null and "b/docs/M\303\270tedokumenter/Internmeeting 2026.02.05.pdf" differ diff --git "a/docs/M\303\270tedokumenter/Internmeeting, 2026.03.19.pdf" "b/docs/M\303\270tedokumenter/Internmeeting, 2026.03.19.pdf" new file mode 100644 index 0000000..2f01609 Binary files /dev/null and "b/docs/M\303\270tedokumenter/Internmeeting, 2026.03.19.pdf" differ diff --git "a/docs/M\303\270tedokumenter/Retroperspektiv 1, 2026.02.12.pdf" "b/docs/M\303\270tedokumenter/Retroperspektiv 1, 2026.02.12.pdf" new file mode 100644 index 0000000..bcb1d3d Binary files /dev/null and "b/docs/M\303\270tedokumenter/Retroperspektiv 1, 2026.02.12.pdf" differ diff --git "a/docs/M\303\270tedokumenter/Retroperspektiv 2, 2026.03.19.pdf" "b/docs/M\303\270tedokumenter/Retroperspektiv 2, 2026.03.19.pdf" new file mode 100644 index 0000000..4a60910 Binary files /dev/null and "b/docs/M\303\270tedokumenter/Retroperspektiv 2, 2026.03.19.pdf" differ diff --git "a/docs/M\303\270tedokumenter/Team meeting 2, 2026.03.17.pdf" "b/docs/M\303\270tedokumenter/Team meeting 2, 2026.03.17.pdf" new file mode 100644 index 0000000..0990c3d Binary files /dev/null and "b/docs/M\303\270tedokumenter/Team meeting 2, 2026.03.17.pdf" differ diff --git "a/docs/M\303\270tedokumenter/Thursdays Meeting 2026.02.19 (With LA).pdf" "b/docs/M\303\270tedokumenter/Thursdays Meeting 2026.02.19 (With LA).pdf" new file mode 100644 index 0000000..8f5beca Binary files /dev/null and "b/docs/M\303\270tedokumenter/Thursdays Meeting 2026.02.19 (With LA).pdf" differ diff --git "a/docs/M\303\270tedokumenter/Thursdays Meeting 2026.03.12 (With LA).pdf" "b/docs/M\303\270tedokumenter/Thursdays Meeting 2026.03.12 (With LA).pdf" new file mode 100644 index 0000000..c6efc55 Binary files /dev/null and "b/docs/M\303\270tedokumenter/Thursdays Meeting 2026.03.12 (With LA).pdf" differ diff --git a/helpmehelpapplication/pom.xml b/helpmehelpapplication/pom.xml index 7b2f137..ec74b68 100644 --- a/helpmehelpapplication/pom.xml +++ b/helpmehelpapplication/pom.xml @@ -33,7 +33,7 @@ org.seleniumhq.selenium selenium-java - 4.41.0 + 4.43.0 com.opencsv diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseManager.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseManager.java new file mode 100644 index 0000000..6276bdf --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/database/DatabaseManager.java @@ -0,0 +1,384 @@ +package ntnu.systemutvikling.team6.database; + +import java.sql.*; +import java.util.*; + +import ntnu.systemutvikling.team6.models.Charity; +import ntnu.systemutvikling.team6.models.CharityRegistry; +import ntnu.systemutvikling.team6.models.Donation; +import ntnu.systemutvikling.team6.models.DonationRegistry; +import ntnu.systemutvikling.team6.scraper.APICharityData; +import ntnu.systemutvikling.team6.scraper.LogoDownloader; +import ntnu.systemutvikling.team6.scraper.URLCharityScraper; + +/** + * Manages the Database with MySQL tables and JDBC. + * + *

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. It + * is also responsible for retrieving the data from the database and sending it to the application + * through the CharityRegistry and DonationRegistry. It is used by the FrontpageController to + * retrieve the data needed to display the charities + */ +public class DatabaseManager { + private final DatabaseConnection connection; + + /** + * Contractor for DatabaseManager. It uses a DatabaseConnection object that contains a connection + * credentials. + */ + 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} and {@code Donations} tables if they do not already exist. + * + *

The table structure for Charities is based on fields from {@link APICharityData}. + * + * @throws RuntimeException if a {@link SQLException} occurs while creating the table + */ + public void createTables() { + String sql_query1 = + """ + -- ----------------------------------------------------- + -- Table `HelpMeHelp`.`Charities` + -- ----------------------------------------------------- + CREATE TABLE IF NOT EXISTS Charities ( + UUID_charities CHAR(36) PRIMARY KEY, + org_number VARCHAR(255) NOT NULL, + charity_name VARCHAR(255) NOT NULL, + charity_link VARCHAR(255) NOT NULL, + pre_approved TINYINT NOT NULL, + status VARCHAR(255) NOT NULL, + description TEXT, + logoURL TEXT, + categories TEXT, + key_values TEXT, + logoBlob MEDIUMBLOB, + UNIQUE KEY unique_org_number (org_number) + ) ENGINE=InnoDB; + + + """; + String sql_query2 = + """ + -- ----------------------------------------------------- + -- Table `HelpMeHelp`.`Donations` + -- ----------------------------------------------------- + CREATE TABLE IF NOT EXISTS Donations ( + `UUID_Donations` CHAR(36) NOT NULL, + `amount` DECIMAL NOT NULL, + `date` DATE NOT NULL, + `Charities_UUID_charities` CHAR(36) NOT NULL, + PRIMARY KEY (`UUID_Donations`), + INDEX `fk_Donations_Charities_idx` (`Charities_UUID_charities` ASC) VISIBLE, + CONSTRAINT `fk_Donations_Charities` + FOREIGN KEY (`Charities_UUID_charities`) + REFERENCES Charities (`UUID_charities`) + ON DELETE CASCADE + ON UPDATE CASCADE) + ENGINE = InnoDB; + """; + + try (Connection conn = connection.getMySqlConnection(); + Statement s = conn.createStatement()) { + + s.execute(sql_query1); + s.execute(sql_query2); + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException("Error creating table."); + } + } + + /** + * This method is used to verify the integrity of the data in the {@code charities} table and to + * update it based on the data retrieved from the IK API and the charity's URL. + * The param charities are retrieved from + * the IK API through the APICharityData class. Called in initialize method in + * HmHApplication.java, which is the main class of the application, to ensure that the data is up + * to date when the application starts. Uses a temp table to ensure that the data in the database + * is consistent with the data from the API. + *

Uses a URLScraper object to get data not contained in the API, and static methods from + * LogoDownloader to get the charity's logo as a blob.

+ * + * @param charities a list of {@code Charity} objects to add to the database + */ + public void addAPIDataToTable(List charities) { + Connection conn = null; + int charityCounter = 0; + + // Scrapes description, logo, categories, and key values from IK + for (Charity charity : charities) { + charityCounter++; + + System.out.println("Scraping charity " + charityCounter + " of " + charities.size()); + URLCharityScraper urlScraper = new URLCharityScraper(charity.getURL()); + urlScraper.scrapeCharityPage(); + + charity.setDescription(urlScraper.getDescription()); + charity.setCategory(urlScraper.getCategories()); + charity.setLogoURL(urlScraper.getLogoURL()); + charity.setKeyValues(urlScraper.getKeyValues()); + byte[] logoBlob = LogoDownloader.downloadImageAsBlob(charity.getLogoURL()); + charity.setLogoBlob(logoBlob); + } + try { + conn = connection.getMySqlConnection(); + conn.setAutoCommit(false); + String sql_query = + """ + INSERT INTO Charities (UUID_charities, org_number, charity_name, charity_link, pre_approved, status, description, logoURL, categories, key_values, logoBlob) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + charity_name = VALUES(charity_name), + charity_link = VALUES(charity_link), + pre_approved = VALUES(pre_approved), + status = VALUES(status), + description = VALUES(description), + logoURL = VALUES(logoURL), + categories = VALUES(categories), + key_values = VALUES(key_values), + logoBlob = VALUES(logoBlob) + """; + + try (PreparedStatement ps = conn.prepareStatement(sql_query)) { + for (Charity charity : charities) { + if (charity.getUUID() == null) { + ps.setString(1, UUID.randomUUID().toString()); + } else { + ps.setString(1, charity.getUUID().toString()); + } + + ps.setString(2, charity.getOrg_number().replaceAll("\\s", "")); + ps.setString(3, charity.getName()); + ps.setString(4, charity.getURL()); + ps.setBoolean(5, charity.getPreApproved()); + ps.setString(6, charity.getStatus()); + ps.setString(7, charity.getDescription()); + ps.setString(8, charity.getLogoURL()); + ps.setString(9, charity.getCategory()); + ps.setString(10, charity.getKeyValues()); + ps.setBytes(11, charity.getLogoBlob()); + + ps.addBatch(); + } + ps.executeBatch(); + } + + // -- Intergerty Check: + String createTemp = + """ + CREATE TEMPORARY TABLE temp_api_charities ( + org_number VARCHAR(20) PRIMARY KEY + ) + """; + + try (PreparedStatement ps = conn.prepareStatement(createTemp)) { + ps.execute(); + } + + String insertTemp = "INSERT IGNORE INTO temp_api_charities (org_number) VALUES (?)"; + + try (PreparedStatement ps = conn.prepareStatement(insertTemp)) { + + for (Charity charity : charities) { + ps.setString(1, charity.getOrg_number().replaceAll("\\s", "")); + ps.addBatch(); + } + + ps.executeBatch(); + } + + String deleteSql = + """ + DELETE FROM Charities c + WHERE NOT EXISTS ( + SELECT 1 + FROM temp_api_charities t + WHERE t.org_number = c.org_number + ) + """; + + try (PreparedStatement ps = conn.prepareStatement(deleteSql)) { + ps.executeUpdate(); + } + + 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(); + } + } + } + } + + /** + * Fetches the data stored in the database and converts it to a list of Charity objects + * in the form of a registry (CharityRegistry). + * + * @return a CharityRegistry of all the charities registered in the database + */ + public CharityRegistry getCharitiesFromDB() { + CharityRegistry registry = null; + Connection conn = null; + try { + conn = connection.getMySqlConnection(); + String sql_query = + "SELECT UUID_charities, org_number, charity_name, charity_link, pre_approved, status, description, logoURL, " + + "categories, key_values, logoBlob FROM Charities"; + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(sql_query); + + registry = new CharityRegistry(); + while (rs.next()) { + Charity charity = + new Charity( + rs.getString("UUID_charities"), + rs.getString("org_number"), + rs.getString("charity_link"), + rs.getString("charity_name"), + rs.getBoolean("pre_approved"), + rs.getString("status")); + charity.setDescription(rs.getString("description")); + charity.setLogoURL(rs.getString("logoURL")); + charity.setCategory(rs.getString("categories")); + charity.setKeyValues(rs.getString("key_values")); + charity.setLogoBlob(rs.getBytes("logoBlob")); + + registry.addCharity(charity); + } + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException("ERROR: Something went wrong during updating charities table."); + } + return registry; + } + + public List getCategoriesFromDB() { + Map categoryMap = new HashMap<>(); + + String sql_query = "SELECT categories FROM Charities"; + + try (Connection conn = connection.getMySqlConnection(); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(sql_query)) { + + while (rs.next()) { + String categoriesStr = rs.getString("categories"); + + if (categoriesStr != null && !categoriesStr.isEmpty()) { + String[] splitCategories = categoriesStr.split(","); + + for (String category : splitCategories) { + String trimmed = category.trim(); + + if (!trimmed.isEmpty()) { + categoryMap.putIfAbsent(trimmed.toLowerCase(), trimmed); + } + } + } + } + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException("ERROR: Something went wrong while fetching categories from the database."); + } + + var categories = new ArrayList<>(categoryMap.values()); + Collections.sort(categories); + + return categories; + } + + public DonationRegistry getDonationFromDB() { + DonationRegistry registry = null; + Connection conn = null; + try { + conn = connection.getMySqlConnection(); + String sql_query = + """ + SELECT + d.UUID_Donations, + d.amount, + d.date, + c.UUID_charities, + c.org_number, + c.charity_name, + c.charity_link, + c.pre_approved, + c.status + FROM Donations d + JOIN Charities c + ON d.Charities_UUID_charities = c.UUID_charities + """; + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(sql_query); + + registry = new DonationRegistry(); + while (rs.next()) { + Charity charity = + new Charity( + rs.getString("UUID_charities"), + rs.getString("org_number"), + rs.getString("charity_name"), + rs.getString("charity_link"), + rs.getBoolean("pre_approved"), + rs.getString("status")); + + Donation donation = + new Donation( + rs.getString("UUID_Donations"), + rs.getDouble("amount"), + rs.getDate("date").toLocalDate(), + charity); + registry.addDonation(donation); + } + } catch (SQLException e) { + e.printStackTrace(); + throw new RuntimeException("ERROR: Something went wrong during updating charities table."); + } + return registry; + } +} \ No newline at end of file diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Charity.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Charity.java index fdae199..357e3ec 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Charity.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/models/Charity.java @@ -20,8 +20,8 @@ public class Charity { /* Name of the charity */ private String name; - /* Description of the charity's mission and activities */ - private String description; + /* URL of the charity */ + private String url; /* Is the charity verified? */ private String status; @@ -31,9 +31,21 @@ public class Charity { /* Category for the charity */ private String category; + /* Description for the charity */ + private String description; + + /* URL for the logo of the charity */ + private String logoURL; + + /* Key values for the charity */ + private String keyValues; + /* List that contains the charity's Feedbacks */ private List feedbacks; + /* Bytecode for the charity logo */ + private byte[] logoBlob; + /** * Contructor for creating a new charity. Taylored to match data given from Api. Other attributes * will just be initialized as empty @@ -48,7 +60,7 @@ public Charity( this.UUID = java.util.UUID.randomUUID(); this.org_number = org_number.replaceAll("\\s", ""); this.name = name; - this.description = "Les mer her: " + link; + this.url = link; this.is_pre_approved = is_pre_approved; this.status = status; this.feedbacks = new ArrayList<>(); @@ -74,11 +86,15 @@ public Charity( this.UUID = UUID.fromString(uuid); this.org_number = org_number.replaceAll("\\s", ""); this.name = name; - this.description = link; + this.url = link; this.is_pre_approved = is_pre_approved; this.status = status; - this.feedbacks = new ArrayList<>(); this.category = ""; + this.description = ""; + this.logoURL = ""; + this.keyValues = ""; + this.feedbacks = new ArrayList<>(); + this.logoBlob = null; } /** Getters for the charity's attributes. */ @@ -110,8 +126,24 @@ public String getName() { return name; } + public String getURL() { + return this.url; + } + public String getDescription() { - return description; + return this.description; + } + + public String getLogoURL() { + return this.logoURL; + } + + public String getKeyValues() { + return this.keyValues; + } + + public byte[] getLogoBlob() { + return this.logoBlob; } /** Setter for verification status. This one sets the charity as verified. */ @@ -124,12 +156,35 @@ public void setUnverified() { this.status = "Veto"; } + /** Setter for categories. */ + public void setCategory(String category) { + this.category = category; + } + + /** Setter for description. */ + public void setDescription(String description) { + this.description = description; + } + + /** Setter for the URL of the charity's logo. */ + public void setLogoURL(String url) { + this.logoURL = url; + } + + /** Setter for the charity's key values. */ + public void setKeyValues(String values) { + this.keyValues = values; + } + + /** Setter for the charity's logo Blob. */ + public void setLogoBlob(byte[] logoBlob) { + this.logoBlob = logoBlob; + } + /** - * Set method for feedbacks. Primarily will be used to set feedbacks taken from database. - * - * @param feedbacks + * Setter for */ - public void setFeedbacks(ArrayList feedbacks) { + public void setFeedbacks(ArrayList feedbacks){ this.feedbacks = feedbacks; } } diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityScraper.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityScraper.java index 89422a1..ac920ac 100644 --- a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityScraper.java +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/APICharityScraper.java @@ -81,6 +81,9 @@ public CharityRegistry parseJSON(String JSONData) { CharityRegistry charityRegistry = new CharityRegistry(); for (APICharityData apiCharityData : charityData) { + if (apiCharityData.getStatus().equalsIgnoreCase("obs")) { + continue; + } Charity charity = new Charity( apiCharityData.getOrg_number(), diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/LogoDownloader.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/LogoDownloader.java new file mode 100644 index 0000000..e190699 --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/LogoDownloader.java @@ -0,0 +1,54 @@ +package ntnu.systemutvikling.team6.scraper; + +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Facilitates downloading of .png images from the individual charity's page on IK, converting them + * to bytecode (blob), and then back to a .png. + */ +public class LogoDownloader { + + /** + * Downloads a image from the given URL and converts it to a blob. + * + * @param imageUrl the URL of the image + * @return a blob containing the image data + */ + public static byte[] downloadImageAsBlob(String imageUrl) { + if (imageUrl == null || imageUrl.isBlank()) return null; + + try (InputStream in = new URL(imageUrl).openStream()) { + return in.readAllBytes(); + } catch (Exception e) { + System.out.println("Error: Something went wrong when downloading the image."); + return null; + } + } + + /** + * Converts a blob of image data back to a .png image file. + * + * @param imageBytes the blob containing the image data + * @param fileName the filename of the .png image file + */ + public static void convertBlobToPNG(byte[] imageBytes, String fileName) { + if (imageBytes == null) { + return; + } + try { + Path folder = Paths.get("target", "logo"); + Files.createDirectories(folder); + + Path filePath = folder.resolve(fileName + ".png"); + + Files.write(filePath, imageBytes); + + } catch (Exception e) { + System.out.println("Error: Something went wrong when converting blob to png."); + } + } +} diff --git a/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/URLCharityScraper.java b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/URLCharityScraper.java new file mode 100644 index 0000000..88be3dd --- /dev/null +++ b/helpmehelpapplication/src/main/java/ntnu/systemutvikling/team6/scraper/URLCharityScraper.java @@ -0,0 +1,288 @@ +package ntnu.systemutvikling.team6.scraper; + +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; + +/** + * Class for scraping the description, URL of the logo, string of categories, and key values of the + * charities registered in IK. + */ +public class URLCharityScraper { + private final String url; + private final WebDriver driver; + private String description; + private String logoURL; + private final List categories; + private final List keyValues; + + /** + * Constructor used for production code. + * + *

It initializes the lists used for categories and keyValues, as well as defining the + * parameters used for the selenium Chromium-based browser that does the scraping. + * + * @param url the URL for the charity's webpage on IK + */ + public URLCharityScraper(String url) { + this.categories = new ArrayList<>(); + this.keyValues = new ArrayList<>(); + + 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"); + + this.url = url; + this.driver = new ChromeDriver(options); + } + + /** + * Constructor used for testing. + * + *

It accepts both a url (should ideally be a dud) and a {@link WebDriver} as parameters. The + * WebDriver is passed to make testing easier. + * + * @param url the URL for the charity's webpage on IK (for this constructor it should not be a + * real URL) + * @param driver the {@code WebDriver} object used for scraping + */ + public URLCharityScraper(String url, WebDriver driver) { + this.categories = new ArrayList<>(); + this.keyValues = new ArrayList<>(); + this.url = url; + this.driver = driver; + } + + /** + * Creates a {@link WebDriverWait} object for halting scraping until the correct pre-conditions + * are met. + * + * @return the {@code WebDriverWait} object to be used in the methods + */ + protected WebDriverWait createWait() { + return new WebDriverWait(driver, Duration.ofSeconds(30)); + } + + /** + * Calls the {@code findElements} method from the {@code WebDriver} object and returns a list of + * the returned {@link WebElement} objects. + * + * @param by a selector for {@code WebElement} objects + * @return a list of found {@code WebElement} objects matching the given selector + */ + protected List findElements(By by) { + return driver.findElements(by); + } + + /** + * Calls the {@code findElement} method from the {@code WebDriver} object and returns a list of + * the returned {@code WebElement} objects. + * + * @param by a selector for {@code WebElement} objects + * @return a list of found {@code WebElement} objects matching the given selector + */ + protected WebElement findElement(By by) { + return driver.findElement(by); + } + + /** Quits the driver instance, making it unusable. */ + protected void closeDriver() { + driver.quit(); + } + + /** Scrapes the URL for the paragraphs containing the description of the charity. */ + protected void updateDescription() { + try { + WebDriverWait wait = createWait(); + StringBuilder descriptionString = new StringBuilder(); + + List readMoreLinks = findElements(By.cssSelector("a.read-more")); + + if (!readMoreLinks.isEmpty()) { + WebElement readMore = findElement(By.cssSelector("a.read-more")); + readMore.click(); + + wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector(".extra-info"))); + } + + wait.until( + ExpectedConditions.numberOfElementsToBeMoreThan(By.cssSelector(".information"), 0)); + + Thread.sleep(5000); + List firstDescription = findElements(By.cssSelector(".information")); + + for (WebElement element : firstDescription) { + if (!element.getText().isBlank()) { + descriptionString.append(element.getText()).append("\n\n"); + } + } + + this.description = descriptionString.toString(); + + } catch (Exception e) { + System.out.println("No description found for " + driver.getCurrentUrl()); + } + } + + /** Scrapes the URL for the image URL of the logo for the charity. */ + void updateLogo() { + try { + WebDriverWait wait = createWait(); + wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector(".logo > img"))); + Thread.sleep(5000); + + WebElement logo = findElement(By.cssSelector(".logo > img")); + this.logoURL = logo.getAttribute("src"); + + } catch (Exception e) { + System.out.println("No logo found for " + driver.getCurrentUrl()); + } + } + + /** Scrapes the URL for the category labels containing the categories for the charity. */ + void updateCategories() { + try { + WebDriverWait wait = createWait(); + + wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector(".tag-label"))); + Thread.sleep(5000); + + List elements = findElements(By.cssSelector(".tag-label")); + + for (WebElement element : elements) { + this.categories.add(element.getText()); + } + + } catch (Exception e) { + System.out.println("No categories found for " + driver.getCurrentUrl()); + } + } + + /** + * Scrapes the URL for the statistics of the charity; the percentage collected, the percentage + * that goes to the administration, and the percentage that is put towards the cause. + */ + void updateKeyValues() { + try { + WebDriverWait wait = createWait(); + + String percentage; + WebElement element; + + wait.until( + ExpectedConditions.visibilityOfElementLocated( + By.xpath( + "//li[.//h2[normalize-space()='Innsamlingsprosent']]//div[@class='graph']"))); + Thread.sleep(5000); + element = + findElement( + By.xpath("//li[.//h2[normalize-space()='Innsamlingsprosent']]//div[@class='graph']")); + percentage = element.getAttribute("data-percentage"); + this.keyValues.add(percentage); + + wait.until( + ExpectedConditions.visibilityOfElementLocated( + By.xpath( + "//li[.//h2[normalize-space()='Administrasjonsprosent']]//div[@class='graph']"))); + + element = + findElement( + By.xpath( + "//li[.//h2[normalize-space()='Administrasjonsprosent']]//div[@class='graph']")); + percentage = element.getAttribute("data-percentage"); + this.keyValues.add(percentage); + + wait.until( + ExpectedConditions.visibilityOfElementLocated( + By.xpath("//li[.//h2[normalize-space()='Formålsprosent']]//div[@class='graph']"))); + + element = + findElement( + By.xpath("//li[.//h2[normalize-space()='Formålsprosent']]//div[@class='graph']")); + percentage = element.getAttribute("data-percentage"); + this.keyValues.add(percentage); + } catch (Exception e) { + System.out.println("No key values found for " + driver.getCurrentUrl()); + } + } + + /** Runs all the scraper methods at once, updating the object parameters. */ + public void scrapeCharityPage() { + try { + driver.get(this.url); + updateDescription(); + updateLogo(); + updateCategories(); + updateKeyValues(); + Thread.sleep(1000); + + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + closeDriver(); + } + } + + /** + * Returns the description of the charity. + * + * @return a String containing the description of the charity. + */ + public String getDescription() { + return description; + } + + /** + * Returns the URL of the logo for the charity. + * + * @return a String containing the URL for the logo of the charity. + */ + public String getLogoURL() { + return logoURL; + } + + /** + * Returns a String of the categories for the charity with ',' as a delimiter. + * + * @return a String of strings containing the categories for the charity + */ + public String getCategories() { + StringBuilder categoriesString = new StringBuilder(); + + for (int i = 0; i < this.categories.size(); i++) { + categoriesString.append(this.categories.get(i)); + if (i < this.categories.size() - 1) { + categoriesString.append(","); + } + } + return categoriesString.toString(); + } + + /** + * Returns a String of the key value percentages for the charity with ':' as a delimiter, verified + * by IK. + * + * @return a String of the key values for the charity- + */ + public String getKeyValues() { + StringBuilder keyValuesString = new StringBuilder(); + + for (int i = 0; i < this.keyValues.size(); i++) { + keyValuesString.append(this.keyValues.get(i)); + if (i < this.keyValues.size() - 1) { + keyValuesString.append(":"); + } + } + return keyValuesString.toString(); + } +} diff --git a/helpmehelpapplication/src/main/resources/fxml/aboutPage.fxml b/helpmehelpapplication/src/main/resources/fxml/aboutPage.fxml index d1ef37c..8e99542 100644 --- a/helpmehelpapplication/src/main/resources/fxml/aboutPage.fxml +++ b/helpmehelpapplication/src/main/resources/fxml/aboutPage.fxml @@ -1,10 +1,8 @@ - - @@ -20,446 +18,466 @@ - + - + - + - + - - - - - - + - - - - + + + - - - - - - - + + + + + - - - + + + + + + + + + - + + - + - + - + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/helpmehelpapplication/src/main/resources/fxml/profile_org_Inbox.fxml b/helpmehelpapplication/src/main/resources/fxml/profile_org_Inbox.fxml new file mode 100644 index 0000000..e369d53 --- /dev/null +++ b/helpmehelpapplication/src/main/resources/fxml/profile_org_Inbox.fxml @@ -0,0 +1,308 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +